diff --git a/Mlem.xcodeproj/project.pbxproj b/Mlem.xcodeproj/project.pbxproj index 95221e496..e6167e153 100644 --- a/Mlem.xcodeproj/project.pbxproj +++ b/Mlem.xcodeproj/project.pbxproj @@ -449,6 +449,7 @@ E4D4DBA22A7F233200C4F3DE /* FancyTabNavigationSelectionHashValueEnvironmentKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D4DBA12A7F233200C4F3DE /* FancyTabNavigationSelectionHashValueEnvironmentKey.swift */; }; E4DDB4322A81819300B3A7E0 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DDB4312A81819300B3A7E0 /* Double.swift */; }; E4DDB4342A819C8000B3A7E0 /* QuickLookView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DDB4332A819C8000B3A7E0 /* QuickLookView.swift */; }; + E4F0B56F2ABD00A000BC3E4A /* PresentationBackgroundInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F0B56E2ABD00A000BC3E4A /* PresentationBackgroundInteraction.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -908,6 +909,7 @@ E4D4DBA12A7F233200C4F3DE /* FancyTabNavigationSelectionHashValueEnvironmentKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyTabNavigationSelectionHashValueEnvironmentKey.swift; sourceTree = ""; }; E4DDB4312A81819300B3A7E0 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = ""; }; E4DDB4332A819C8000B3A7E0 /* QuickLookView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickLookView.swift; sourceTree = ""; }; + E4F0B56E2ABD00A000BC3E4A /* PresentationBackgroundInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationBackgroundInteraction.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1388,6 +1390,7 @@ 508845CE2A3641160088E483 /* JSONDecoder+Default.swift */, B1A26FE02A44AAB200B91A32 /* Navigation getter.swift */, B104A6DF2A59C19400B3E725 /* OperationQueue - Easy init.swift */, + E4F0B56E2ABD00A000BC3E4A /* PresentationBackgroundInteraction.swift */, 6386E0392A0455BC006B3C1D /* String - Contains Elements From Array.swift */, 630737882A1CD1E900039852 /* String.swift */, 5064D0402A6E63E000B22EE3 /* Task+Notifiable.swift */, @@ -2682,6 +2685,7 @@ 505240E72A88D36D00EA4558 /* SectionIndexTitles.swift in Sources */, 5064D0452A71549C00B22EE3 /* NotificationMessage.swift in Sources */, 63344C4D2A07ABEE001BC616 /* Community.swift in Sources */, + E4F0B56F2ABD00A000BC3E4A /* PresentationBackgroundInteraction.swift in Sources */, CDF842612A49EA3900723DA0 /* Mentions Tracker.swift in Sources */, 6D693A4C2A51B99E009E2D76 /* APICommentReport.swift in Sources */, 63344C672A08D4E3001BC616 /* AppearanceSettingsView.swift in Sources */, diff --git a/Mlem/API/Requests/Person/GetPersonDetails.swift b/Mlem/API/Requests/Person/GetPersonDetails.swift index a16f5654d..64072843f 100644 --- a/Mlem/API/Requests/Person/GetPersonDetails.swift +++ b/Mlem/API/Requests/Person/GetPersonDetails.swift @@ -49,7 +49,9 @@ struct GetPersonDetailsRequest: APIGetRequest { guard let host = instanceURL.host() else { throw GetPersonDetailsRequestError.unableToDetermineInstanceHost } - username = "\(username)@\(host)" + + // when logging into a locally running instance, we don't want to pass `user@localhost` + username = host == "localhost" ? username : "\(username)@\(host)" } queryItems.append(.init(name: "username", value: username)) diff --git a/Mlem/App Constants.swift b/Mlem/App Constants.swift index 232b81bec..c8c14344a 100644 --- a/Mlem/App Constants.swift +++ b/Mlem/App Constants.swift @@ -32,6 +32,7 @@ struct AppConstants { static let maxFeedPostHeight: CGFloat = 400 static let maxFeedPostHeightExpanded: CGFloat = 3000 + static let appIconSize: CGFloat = 60 static let thumbnailSize: CGFloat = 60 static let hugeAvatarSize: CGFloat = 120 static let largeAvatarSize: CGFloat = 32 @@ -40,6 +41,7 @@ struct AppConstants { static let largeAvatarSpacing: CGFloat = 10 static let postAndCommentSpacing: CGFloat = 10 // standard spacing for the app static let compactSpacing: CGFloat = 6 // standard spacing for compact things + static let appIconCornerRadius: CGFloat = 10 static let largeItemCornerRadius: CGFloat = 8 // posts, website previews, etc static let smallItemCornerRadius: CGFloat = 6 // settings items, compact thumbnails static let tinyItemCornerRadius: CGFloat = 4 // buttons diff --git a/Mlem/Assets.xcassets/Icons/Classic Lemmy by Eric Andrews.appiconset/Classic Lemmy.png b/Mlem/Assets.xcassets/Icons/Classic Lemmy by Eric Andrews.appiconset/Classic Lemmy.png index 3168e6366..8ce606adf 100644 Binary files a/Mlem/Assets.xcassets/Icons/Classic Lemmy by Eric Andrews.appiconset/Classic Lemmy.png and b/Mlem/Assets.xcassets/Icons/Classic Lemmy by Eric Andrews.appiconset/Classic Lemmy.png differ diff --git a/Mlem/ContentView.swift b/Mlem/ContentView.swift index e16ac0c7a..24df7013b 100644 --- a/Mlem/ContentView.swift +++ b/Mlem/ContentView.swift @@ -119,11 +119,15 @@ struct ContentView: View { NavigationStack { ResponseEditorView(concreteEditorModel: editing) } + .presentationDetents([.medium, .large]) + ._presentationBackgroundInteraction(enabledUpThrough: .medium) } .sheet(item: $editorTracker.editPost) { editing in NavigationStack { PostComposerView(editModel: editing) } + .presentationDetents([.medium, .large]) + ._presentationBackgroundInteraction(enabledUpThrough: .medium) } .environment(\.openURL, OpenURLAction(handler: didReceiveURL)) .environmentObject(editorTracker) diff --git a/Mlem/Extensions/AlternativeIconCell.swift b/Mlem/Extensions/AlternativeIconCell.swift index 27b15fab3..a526273f1 100644 --- a/Mlem/Extensions/AlternativeIconCell.swift +++ b/Mlem/Extensions/AlternativeIconCell.swift @@ -21,9 +21,13 @@ struct AlternativeIconCell: View { getImage() .resizable() .scaledToFit() - .frame(width: 60, height: 60) + .frame(width: AppConstants.appIconSize, height: AppConstants.appIconSize) .foregroundColor(Color.white) - .cornerRadius(10.0) + .cornerRadius(AppConstants.appIconCornerRadius) + .overlay { + RoundedRectangle(cornerRadius: AppConstants.appIconCornerRadius) + .stroke(Color(.secondarySystemBackground), lineWidth: 1) + } VStack(alignment: .leading) { Text(icon.name) if let author = icon.author { diff --git a/Mlem/Extensions/PresentationBackgroundInteraction.swift b/Mlem/Extensions/PresentationBackgroundInteraction.swift new file mode 100644 index 000000000..66bc5e125 --- /dev/null +++ b/Mlem/Extensions/PresentationBackgroundInteraction.swift @@ -0,0 +1,20 @@ +// +// PresentationBackgroundInteraction.swift +// Mlem +// +// Created by Bosco Ho on 2023-09-21. +// + +import SwiftUI + +extension View { + + /// No-op prior to iOS 16.4. + func _presentationBackgroundInteraction(enabledUpThrough detent: PresentationDetent) -> some View { + if #available(iOS 16.4, *) { + return self.presentationBackgroundInteraction(.enabled(upThrough: detent)) + } else { + return self + } + } +} diff --git a/Mlem/Views/Shared/Accounts/Instance Picker View.swift b/Mlem/Views/Shared/Accounts/Instance Picker View.swift index 8e25896d0..7ec024ee3 100644 --- a/Mlem/Views/Shared/Accounts/Instance Picker View.swift +++ b/Mlem/Views/Shared/Accounts/Instance Picker View.swift @@ -15,38 +15,62 @@ struct InstancePickerView: View { @Binding var selectedInstance: InstanceMetadata? - @State var instances: [InstanceMetadata]? + @State private var query: String = "" + @State private var instances: [InstanceMetadata]? /// Instances currently accepting new users - var filteredInstances: [InstanceMetadata]? { + var filteredInstances: ArraySlice? { instances? - .filter { instance in - instance.newUsers - } + .filter { query.isEmpty || $0.name.lowercased().hasPrefix(query.lowercased()) } + .prefix(30) // limit to a maximum of 30 } let onboarding: Bool var body: some View { ScrollView { - LazyVStack(spacing: 0) { + VStack(spacing: .zero) { if onboarding { Text(pickInstance) .frame(maxWidth: .infinity) .padding() } + HStack { + Image(systemName: "magnifyingglass") + .foregroundColor(.secondary) + TextField("Search", text: $query, prompt: Text("Looking for a specific instance?")) + .autocorrectionDisabled() + .textInputAutocapitalization(.never) + .textFieldStyle(.roundedBorder) + } + .padding(.horizontal) + .padding(.bottom, 16) + if let filteredInstances { - ForEach(filteredInstances) { instance in - VStack(spacing: 0) { - Divider() - - InstanceSummary( - instance: instance, - onboarding: true, - selectedInstance: $selectedInstance - ) - .padding(.horizontal) + if filteredInstances.isEmpty { + VStack(spacing: 16) { + Spacer() + Text("There are no results for \(query)") + Button { + query = "" + } label: { + Text("Clear") + } + Spacer() + } + } else { + ForEach(filteredInstances) { instance in + VStack(spacing: .zero) { + Divider() + + InstanceSummary( + instance: instance, + onboarding: true, + selectedInstance: $selectedInstance + ) + .padding(.horizontal) + } } } } else { @@ -54,9 +78,15 @@ struct InstancePickerView: View { } } } + .scrollDismissesKeyboard(.immediately) .navigationTitle("Instances") .task { instances = await loadInstances() + // remote source is already sorted by user count but that may change... + .sorted(by: { $0.users > $1.users }) + // restrict the list to instances who are currently ccepting new users + // also filter on the presence of `nsfw` in the version string + .filter { $0.newUsers && !$0.version.contains("nsfw") } } } } diff --git a/Mlem/Views/Shared/Composer/PostComposerView.swift b/Mlem/Views/Shared/Composer/PostComposerView.swift index 363f70a04..1ae1f647f 100644 --- a/Mlem/Views/Shared/Composer/PostComposerView.swift +++ b/Mlem/Views/Shared/Composer/PostComposerView.swift @@ -22,6 +22,10 @@ struct PostComposerView: View { @State var postBody: String @State var isNSFW: Bool + private var hasPostContent: Bool { + !postTitle.isEmpty || !postURL.isEmpty || !postBody.isEmpty + } + init(editModel: PostEditorModel) { self.postTracker = editModel.postTracker self.editModel = editModel @@ -67,6 +71,7 @@ struct PostComposerView: View { dismiss() } + .interactiveDismissDisabled(hasPostContent) } } diff --git a/Mlem/Views/Shared/Composer/ResponseEditorView.swift b/Mlem/Views/Shared/Composer/ResponseEditorView.swift index e5e2217ae..6967dce33 100644 --- a/Mlem/Views/Shared/Composer/ResponseEditorView.swift +++ b/Mlem/Views/Shared/Composer/ResponseEditorView.swift @@ -100,6 +100,7 @@ struct ResponseEditorView: View { .navigationBarColor() .navigationTitle(editorModel.modalName) .navigationBarTitleDisplayMode(.inline) + .interactiveDismissDisabled(isReadyToReply) } @MainActor diff --git a/Mlem/Views/Shared/Website Icon Complex.swift b/Mlem/Views/Shared/Website Icon Complex.swift index bd99e2f35..a70cb06f7 100644 --- a/Mlem/Views/Shared/Website Icon Complex.swift +++ b/Mlem/Views/Shared/Website Icon Complex.swift @@ -63,7 +63,7 @@ struct WebsiteIconComplex: View { var imageHeight: CGFloat { horizontalSizeClass == .regular ? 400 : screenWidth * 0.66 } var body: some View { - VStack(spacing: 0) { + LazyVStack(spacing: 0) { if shouldShowWebsitePreviews, let thumbnailURL = post.thumbnailImageUrl { CachedImage( url: thumbnailURL, diff --git a/Mlem/Views/Tabs/Settings/Components/Settings Item.swift b/Mlem/Views/Tabs/Settings/Components/Settings Item.swift index 2bfe828b0..4c57f14f0 100644 --- a/Mlem/Views/Tabs/Settings/Components/Settings Item.swift +++ b/Mlem/Views/Tabs/Settings/Components/Settings Item.swift @@ -73,14 +73,26 @@ struct SelectableSettingsItem: View { struct SquircleLabelStyle: LabelStyle { var color: Color - var fontSize: CGFloat = 17 + 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 + } + } func makeBody(configuration: Configuration) -> some View { Label { configuration.title } icon: { configuration.icon - .font(.system(size: fontSize)) + .font(.system(size: adjustedFontSize)) .foregroundColor(.white) .frame(width: AppConstants.settingsIconSize, height: AppConstants.settingsIconSize) .background(color) diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift index 9bd166de1..27619eac2 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift @@ -26,7 +26,7 @@ struct AppearanceSettingsView: View { } } #if !os(macOS) && !targetEnvironment(macCatalyst) - NavigationLink(value: SettingsRoute.appearancePage(.appIcon)) { + NavigationLink(value: SettingsRoute.appearancePage(.appIcon)) { Label { Text("App Icon") } icon: { @@ -35,6 +35,10 @@ struct AppearanceSettingsView: View { .scaledToFit() .frame(width: AppConstants.settingsIconSize, height: AppConstants.settingsIconSize) .cornerRadius(AppConstants.smallItemCornerRadius) + .overlay { + RoundedRectangle(cornerRadius: AppConstants.smallItemCornerRadius) + .stroke(Color(.secondarySystemBackground), lineWidth: 1) + } } } #endif