diff --git a/Shared/Extensions/String.swift b/Shared/Extensions/String.swift index cdd9abbc..77813789 100644 --- a/Shared/Extensions/String.swift +++ b/Shared/Extensions/String.swift @@ -90,11 +90,13 @@ extension String { .replacingOccurrences(of: ".swift", with: "") } + // TODO: fix if count > 62 static func random(count: Int) -> String { let characters = Self.alphanumeric.randomSample(count: count) return String(characters) } + // TODO: fix if upper bound > 62 static func random(count range: Range) -> String { let characters = Self.alphanumeric.randomSample(count: Int.random(in: range)) return String(characters) @@ -114,6 +116,20 @@ extension String { return s } + + // TODO: remove after iOS 15 support removed + + func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat { + let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude) + let boundingBox = self.boundingRect( + with: constraintRect, + options: .usesLineFragmentOrigin, + attributes: [.font: font], + context: nil + ) + + return ceil(boundingBox.height) + } } extension CharacterSet { diff --git a/Shared/Extensions/ViewExtensions/Backport/Backport.swift b/Shared/Extensions/ViewExtensions/Backport/Backport.swift index cecb99d7..f3b8b369 100644 --- a/Shared/Extensions/ViewExtensions/Backport/Backport.swift +++ b/Shared/Extensions/ViewExtensions/Backport/Backport.swift @@ -31,8 +31,16 @@ extension Backport where Content: View { content .lineLimit(limit, reservesSpace: reservesSpace) } else { + // This may still not be enough and will probably have to + // use String.height in a frame as caller site + // + // The `.font` modifier must come after this modifier in + // order for the layout and content fonts to match ZStack(alignment: .top) { - Text(String(repeating: " \n", count: limit)) + if reservesSpace { + Text("A" + String(repeating: "A\nA", count: limit - 1)) + .hidden() + } content .lineLimit(limit) diff --git a/Shared/Extensions/ViewExtensions/Modifiers/OnSizeChangedModifier.swift b/Shared/Extensions/ViewExtensions/Modifiers/OnSizeChangedModifier.swift index 6212f48c..ef07415e 100644 --- a/Shared/Extensions/ViewExtensions/Modifiers/OnSizeChangedModifier.swift +++ b/Shared/Extensions/ViewExtensions/Modifiers/OnSizeChangedModifier.swift @@ -37,7 +37,5 @@ struct EnvironmentModifier: ViewModifier { func body(content: Content) -> some View { wrapped(environmentValue) - -// wrapped(content) } } diff --git a/Shared/Services/UserSession.swift b/Shared/Services/UserSession.swift index e4f14ff0..d00fc321 100644 --- a/Shared/Services/UserSession.swift +++ b/Shared/Services/UserSession.swift @@ -42,24 +42,27 @@ final class UserSession { extension Container { var currentUserSession: Factory { self { - if let lastUserID = Defaults[.lastSignedInUserID], - let user = try? SwiftfinStore.dataStack.fetchOne( - From().where(\.$id == lastUserID) - ) - { - guard let server = user.server, - let _ = SwiftfinStore.dataStack.fetchExisting(server) - else { - fatalError("No associated server for last user") - } + guard let lastUserID = Defaults[.lastSignedInUserID] else { return nil } - return .init( - server: server.state, - user: user.state - ) + guard let user = try? SwiftfinStore.dataStack.fetchOne( + From().where(\.$id == lastUserID) + ) else { + // had last user ID but no saved user + Defaults[.lastSignedInUserID] = nil + + return nil } - return nil + guard let server = user.server, + let _ = SwiftfinStore.dataStack.fetchExisting(server) + else { + fatalError("No associated server for last user") + } + + return .init( + server: server.state, + user: user.state + ) }.cached } } diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index e7be57eb..584379c3 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -824,6 +824,7 @@ E1DABAFA2A270E62008AC34A /* OverviewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DABAF92A270E62008AC34A /* OverviewCard.swift */; }; E1DABAFC2A270EE7008AC34A /* MediaSourcesCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DABAFB2A270EE7008AC34A /* MediaSourcesCard.swift */; }; E1DABAFE2A27B982008AC34A /* RatingsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DABAFD2A27B982008AC34A /* RatingsCard.swift */; }; + E1DC7ACA2C63337C00AEE368 /* iOS15View.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DC7AC92C63337C00AEE368 /* iOS15View.swift */; }; E1DC9814296DC06200982F06 /* PulseLogHandler in Frameworks */ = {isa = PBXBuildFile; productRef = E1DC9813296DC06200982F06 /* PulseLogHandler */; }; E1DC981A296DD1CD00982F06 /* CinematicBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DC9818296DD1CD00982F06 /* CinematicBackgroundView.swift */; }; E1DC983D296DEB9B00982F06 /* UnwatchedIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DC983C296DEB9B00982F06 /* UnwatchedIndicator.swift */; }; @@ -1485,6 +1486,7 @@ E1DABAF92A270E62008AC34A /* OverviewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewCard.swift; sourceTree = ""; }; E1DABAFB2A270EE7008AC34A /* MediaSourcesCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaSourcesCard.swift; sourceTree = ""; }; E1DABAFD2A27B982008AC34A /* RatingsCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingsCard.swift; sourceTree = ""; }; + E1DC7AC92C63337C00AEE368 /* iOS15View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOS15View.swift; sourceTree = ""; }; E1DC9818296DD1CD00982F06 /* CinematicBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicBackgroundView.swift; sourceTree = ""; }; E1DC983C296DEB9B00982F06 /* UnwatchedIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnwatchedIndicator.swift; sourceTree = ""; }; E1DC9840296DEBD800982F06 /* WatchedIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchedIndicator.swift; sourceTree = ""; }; @@ -2100,6 +2102,7 @@ E1DE2B492B97ECB900F6715F /* ErrorView.swift */, E1921B7528E63306003A5238 /* GestureView.swift */, E178B0752BE435D70023651B /* HourMinutePicker.swift */, + E1DC7AC92C63337C00AEE368 /* iOS15View.swift */, E1FE69A928C29CC20021BC93 /* LandscapePosterProgressBar.swift */, 4E16FD4E2C0183B500110147 /* LetterPickerBar */, E1A8FDEB2C0574A800D0A51C /* ListRow.swift */, @@ -4571,6 +4574,7 @@ E1BDF2F329524C3B00CC0294 /* ChaptersActionButton.swift in Sources */, E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */, E1BE1CF02BDB6C97008176A9 /* UserProfileSettingsView.swift in Sources */, + E1DC7ACA2C63337C00AEE368 /* iOS15View.swift in Sources */, E1CFE28028FA606800B7D34C /* ChapterTrack.swift in Sources */, E1401CA22938122C00E8B599 /* AppIcons.swift in Sources */, E1BDF2FB2952502300CC0294 /* SubtitleActionButton.swift in Sources */, diff --git a/Swiftfin/App/SwiftfinApp.swift b/Swiftfin/App/SwiftfinApp.swift index 1e200a5d..eabd79dd 100644 --- a/Swiftfin/App/SwiftfinApp.swift +++ b/Swiftfin/App/SwiftfinApp.swift @@ -66,7 +66,7 @@ struct SwiftfinApp: App { // Swiftfin // don't keep last user id - if Defaults[.signOutOnClose] { + if Defaults[.signOutOnClose] || Container.shared.currentUserSession() == nil { Defaults[.lastSignedInUserID] = nil } } diff --git a/Swiftfin/Components/PosterButton.swift b/Swiftfin/Components/PosterButton.swift index 56e90560..f282721c 100644 --- a/Swiftfin/Components/PosterButton.swift +++ b/Swiftfin/Components/PosterButton.swift @@ -140,16 +140,36 @@ extension PosterButton { let item: Item var body: some View { - VStack(alignment: .leading) { - if item.showTitle { - TitleContentView(item: item) + iOS15View { + VStack(alignment: .leading, spacing: 0) { + if item.showTitle { + TitleContentView(item: item) + .backport + .lineLimit(1, reservesSpace: true) + .iOS15 { v in + v.font(.footnote.weight(.regular)) + } + } + + SubtitleContentView(item: item) + .backport + .lineLimit(1, reservesSpace: true) + .iOS15 { v in + v.font(.caption.weight(.medium)) + } + } + } content: { + VStack(alignment: .leading) { + if item.showTitle { + TitleContentView(item: item) + .backport + .lineLimit(1, reservesSpace: true) + } + + SubtitleContentView(item: item) .backport .lineLimit(1, reservesSpace: true) } - - SubtitleContentView(item: item) - .backport - .lineLimit(1, reservesSpace: true) } } } diff --git a/Swiftfin/Components/iOS15View.swift b/Swiftfin/Components/iOS15View.swift new file mode 100644 index 00000000..88cd13ec --- /dev/null +++ b/Swiftfin/Components/iOS15View.swift @@ -0,0 +1,24 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024 Jellyfin & Jellyfin Contributors +// + +import SwiftUI + +// TODO: remove when iOS 15 support removed +struct iOS15View: View { + + let iOS15: () -> iOS15Content + let content: () -> Content + + var body: some View { + if #available(iOS 16, *) { + content() + } else { + iOS15() + } + } +} diff --git a/Swiftfin/Extensions/View/View-iOS.swift b/Swiftfin/Extensions/View/View-iOS.swift index afb88203..e54aeeb7 100644 --- a/Swiftfin/Extensions/View/View-iOS.swift +++ b/Swiftfin/Extensions/View/View-iOS.swift @@ -11,6 +11,17 @@ import SwiftUI extension View { + // TODO: remove after removing support for iOS 15 + + @ViewBuilder + func iOS15(@ViewBuilder _ content: (Self) -> Content) -> some View { + if #available(iOS 16, *) { + self + } else { + content(self) + } + } + func detectOrientation(_ orientation: Binding) -> some View { modifier(DetectOrientation(orientation: orientation)) } diff --git a/Swiftfin/Views/ChannelLibraryView/Components/CompactChannelView.swift b/Swiftfin/Views/ChannelLibraryView/Components/CompactChannelView.swift index 8e33c7c8..8bd05867 100644 --- a/Swiftfin/Views/ChannelLibraryView/Components/CompactChannelView.swift +++ b/Swiftfin/Views/ChannelLibraryView/Components/CompactChannelView.swift @@ -52,6 +52,7 @@ extension ChannelLibraryView { .foregroundColor(.primary) .backport .lineLimit(1, reservesSpace: true) + .font(.footnote.weight(.regular)) } } .buttonStyle(.plain) diff --git a/Swiftfin/Views/ItemView/Components/EpisodeSelector/Components/EpisodeContent.swift b/Swiftfin/Views/ItemView/Components/EpisodeSelector/Components/EpisodeContent.swift index b1c7625e..f0fb0c4f 100644 --- a/Swiftfin/Views/ItemView/Components/EpisodeSelector/Components/EpisodeContent.swift +++ b/Swiftfin/Views/ItemView/Components/EpisodeSelector/Components/EpisodeContent.swift @@ -49,6 +49,7 @@ extension SeriesEpisodeSelector { .multilineTextAlignment(.leading) .backport .lineLimit(3, reservesSpace: true) + .font(.caption.weight(.light)) } var body: some View { @@ -61,6 +62,14 @@ extension SeriesEpisodeSelector { headerView contentView + .iOS15 { v in + v.frame( + height: "A\nA\nA".height( + withConstrainedWidth: 10, + font: Font.caption.uiFont + ) + ) + } L10n.seeMore.text .font(.caption.weight(.light)) diff --git a/Swiftfin/Views/ItemView/Components/EpisodeSelector/EpisodeSelector.swift b/Swiftfin/Views/ItemView/Components/EpisodeSelector/EpisodeSelector.swift index 28ab0971..a1d6eb90 100644 --- a/Swiftfin/Views/ItemView/Components/EpisodeSelector/EpisodeSelector.swift +++ b/Swiftfin/Views/ItemView/Components/EpisodeSelector/EpisodeSelector.swift @@ -43,7 +43,6 @@ struct SeriesEpisodeSelector: View { ) .labelStyle(.episodeSelector) } - .fixedSize() } var body: some View {