From bbebf148373d1ee23d17b8581db461d0c66fd8d0 Mon Sep 17 00:00:00 2001 From: Ibrahim Sulejmenov Date: Sat, 15 Mar 2025 22:02:28 +0100 Subject: [PATCH 1/4] added setting to change anime per row in landscape and portrait modes + fixed stretched images --- Sora/Utils/SkeletonCells/SkeletonCell.swift | 12 +++-- Sora/Views/LibraryView/LibraryView.swift | 40 ++++++++++++++--- Sora/Views/SearchView.swift | 44 ++++++++++++++++--- .../SettingsViewGeneral.swift | 42 ++++++++++++++++++ 4 files changed, 121 insertions(+), 17 deletions(-) diff --git a/Sora/Utils/SkeletonCells/SkeletonCell.swift b/Sora/Utils/SkeletonCells/SkeletonCell.swift index adf330a..deb6c67 100644 --- a/Sora/Utils/SkeletonCells/SkeletonCell.swift +++ b/Sora/Utils/SkeletonCells/SkeletonCell.swift @@ -8,17 +8,19 @@ import SwiftUI struct HomeSkeletonCell: View { + let cellWidth: CGFloat + var body: some View { VStack { RoundedRectangle(cornerRadius: 10) .fill(Color.gray.opacity(0.3)) - .frame(width: 130, height: 195) + .frame(width: cellWidth, height: cellWidth * 1.5) // Maintains 2:3 aspect ratio .cornerRadius(10) .shimmering() RoundedRectangle(cornerRadius: 5) .fill(Color.gray.opacity(0.3)) - .frame(width: 130, height: 20) + .frame(width: cellWidth, height: 20) .padding(.top, 4) .shimmering() } @@ -26,15 +28,17 @@ struct HomeSkeletonCell: View { } struct SearchSkeletonCell: View { + let cellWidth: CGFloat + var body: some View { VStack(alignment: .leading, spacing: 8) { RoundedRectangle(cornerRadius: 10) .fill(Color.gray.opacity(0.3)) - .frame(width: 150, height: 225) + .frame(width: cellWidth, height: cellWidth * 1.5) // Maintains 2:3 aspect ratio .shimmering() RoundedRectangle(cornerRadius: 5) .fill(Color.gray.opacity(0.3)) - .frame(width: 150, height: 20) + .frame(width: cellWidth, height: 20) .shimmering() } } diff --git a/Sora/Views/LibraryView/LibraryView.swift b/Sora/Views/LibraryView/LibraryView.swift index 7bd8130..4d2a104 100644 --- a/Sora/Views/LibraryView/LibraryView.swift +++ b/Sora/Views/LibraryView/LibraryView.swift @@ -12,7 +12,13 @@ struct LibraryView: View { @EnvironmentObject private var libraryManager: LibraryManager @EnvironmentObject private var moduleManager: ModuleManager + @AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2 + @AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4 + + @Environment(\.verticalSizeClass) var verticalSizeClass + @State private var continueWatchingItems: [ContinueWatchingItem] = [] + @State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape private let columns = [ GridItem(.adaptive(minimum: 150), spacing: 12) @@ -21,6 +27,8 @@ struct LibraryView: View { var body: some View { NavigationView { ScrollView { + let columnsCount = determineColumns() + VStack(alignment: .leading, spacing: 12) { Text("Continue Watching") .font(.title2) @@ -67,22 +75,23 @@ struct LibraryView: View { .padding() .frame(maxWidth: .infinity) } else { - LazyVGrid(columns: columns, spacing: 12) { + LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 12), count: columnsCount), spacing: 12) { ForEach(libraryManager.bookmarks) { item in if let module = moduleManager.modules.first(where: { $0.id.uuidString == item.moduleId }) { NavigationLink(destination: MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: module)) { - VStack { + VStack(alignment: .leading) { ZStack { KFImage(URL(string: item.imageUrl)) .placeholder { RoundedRectangle(cornerRadius: 10) .fill(Color.gray.opacity(0.3)) - .frame(width: 150, height: 225) + .aspectRatio(2/3, contentMode: .fit) .shimmering() } .resizable() .aspectRatio(2/3, contentMode: .fill) - .frame(width: 150, height: 225) + // Allow the image to expand to fill the available width of its grid cell. + .frame(maxWidth: .infinity) .cornerRadius(10) .clipped() .overlay( @@ -94,11 +103,10 @@ struct LibraryView: View { alignment: .topLeading ) } - Text(item.title) .font(.subheadline) .foregroundColor(.primary) - .lineLimit(2) + .lineLimit(1) .multilineTextAlignment(.leading) } } @@ -106,6 +114,12 @@ struct LibraryView: View { } } .padding(.horizontal, 20) + .onAppear { + updateOrientation() + } + .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + updateOrientation() + } } } .padding(.vertical, 20) @@ -135,6 +149,20 @@ struct LibraryView: View { ContinueWatchingManager.shared.remove(item: item) continueWatchingItems.removeAll { $0.id == item.id } } + + private func updateOrientation() { + DispatchQueue.main.async { + isLandscape = UIDevice.current.orientation.isLandscape + } + } + + private func determineColumns() -> Int { + if UIDevice.current.userInterfaceIdiom == .pad { + return isLandscape ? mediaColumnsLandscape : mediaColumnsPortrait + } else { + return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait + } + } } struct ContinueWatchingSection: View { diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView.swift index 914b585..de0bb1b 100644 --- a/Sora/Views/SearchView.swift +++ b/Sora/Views/SearchView.swift @@ -17,14 +17,19 @@ struct SearchItem: Identifiable { struct SearchView: View { @AppStorage("selectedModuleId") private var selectedModuleId: String? + @AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2 + @AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4 + @StateObject private var jsController = JSController() @EnvironmentObject var moduleManager: ModuleManager + @Environment(\.verticalSizeClass) var verticalSizeClass @State private var searchItems: [SearchItem] = [] @State private var selectedSearchItem: SearchItem? @State private var isSearching = false @State private var searchText = "" @State private var hasNoResults = false + @State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape private var selectedModule: ScrapingModule? { guard let id = selectedModuleId else { return nil } @@ -38,10 +43,12 @@ struct SearchView: View { "Please wait...", "Almost there..." ] - + var body: some View { NavigationView { ScrollView { + let columnsCount = determineColumns() + VStack(spacing: 0) { HStack { SearchBar(text: $searchText, onSearchButtonClicked: performSearch) @@ -79,9 +86,13 @@ struct SearchView: View { if !searchText.isEmpty { if isSearching { - LazyVGrid(columns: [GridItem(.adaptive(minimum: 150))], spacing: 16) { - ForEach(0..<2, id: \.self) { _ in - SearchSkeletonCell() + let totalSpacing: CGFloat = 16 * CGFloat(columnsCount + 1) // Spacing between items + let availableWidth = UIScreen.main.bounds.width - totalSpacing + let cellWidth = availableWidth / CGFloat(columnsCount) + + LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) { + ForEach(0.. Int { + if UIDevice.current.userInterfaceIdiom == .pad { + return isLandscape ? mediaColumnsLandscape : mediaColumnsPortrait + } else { + return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait + } + } } struct SearchBar: View { diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift index c9bbfbd..65f7bfe 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift @@ -14,6 +14,9 @@ struct SettingsViewGeneral: View { @AppStorage("analyticsEnabled") private var analyticsEnabled: Bool = false @AppStorage("multiThreads") private var multiThreadsEnabled: Bool = false @AppStorage("metadataProviders") private var metadataProviders: String = "AniList" + @AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2 + @AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4 + private let metadataProvidersList = ["AniList"] @EnvironmentObject var settings: Settings @@ -76,6 +79,45 @@ struct SettingsViewGeneral: View { // .tint(.accentColor) //} + Section(header: Text("Media Grid Layout"), footer: Text("Adjust the number of media items per row in portrait and landscape modes.")) { + HStack { + Spacer() + if UIDevice.current.userInterfaceIdiom == .pad { + Picker("Portrait Columns", selection: $mediaColumnsPortrait) { + ForEach(1..<6) { i in + Text("\(i)").tag(i) + } + } + .pickerStyle(MenuPickerStyle()) + } else { + Picker("Portrait Columns", selection: $mediaColumnsPortrait) { + ForEach(1..<5) { i in + Text("\(i)").tag(i) + } + } + .pickerStyle(MenuPickerStyle()) + } + } + HStack { + Spacer() + if UIDevice.current.userInterfaceIdiom == .pad { + Picker("Landscape Columns", selection: $mediaColumnsLandscape) { + ForEach(2..<9) { i in + Text("\(i)").tag(i) + } + } + .pickerStyle(MenuPickerStyle()) + } else { + Picker("Landscape Columns", selection: $mediaColumnsLandscape) { + ForEach(2..<6) { i in + Text("\(i)").tag(i) + } + } + .pickerStyle(MenuPickerStyle()) + } + } + } + Section(header: Text("Modules"), footer: Text("Note that the modules will be replaced only if there is a different version string inside the JSON file.")) { Toggle("Refresh Modules on Launch", isOn: $refreshModulesOnLaunch) .tint(.accentColor) From 6d6bc33dfe9636b0659448919fafca976afdf6e5 Mon Sep 17 00:00:00 2001 From: Ibrahim Sulejmenov Date: Sat, 15 Mar 2025 22:04:49 +0100 Subject: [PATCH 2/4] added setting to change anime per row in landscape and portrait modes + fixed stretched images 2 --- Sora/Views/LibraryView/LibraryView.swift | 10 +++++++--- Sora/Views/MediaInfoView/MediaInfoView.swift | 5 +++-- Sora/Views/SearchView.swift | 10 ++++++++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Sora/Views/LibraryView/LibraryView.swift b/Sora/Views/LibraryView/LibraryView.swift index 4d2a104..396300c 100644 --- a/Sora/Views/LibraryView/LibraryView.swift +++ b/Sora/Views/LibraryView/LibraryView.swift @@ -76,6 +76,10 @@ struct LibraryView: View { .frame(maxWidth: .infinity) } else { LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 12), count: columnsCount), spacing: 12) { + let totalSpacing: CGFloat = 16 * CGFloat(columnsCount + 1) // Spacing between items + let availableWidth = UIScreen.main.bounds.width - totalSpacing + let cellWidth = availableWidth / CGFloat(columnsCount) + ForEach(libraryManager.bookmarks) { item in if let module = moduleManager.modules.first(where: { $0.id.uuidString == item.moduleId }) { NavigationLink(destination: MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: module)) { @@ -89,9 +93,9 @@ struct LibraryView: View { .shimmering() } .resizable() - .aspectRatio(2/3, contentMode: .fill) - // Allow the image to expand to fill the available width of its grid cell. - .frame(maxWidth: .infinity) + .aspectRatio(contentMode: .fill) + .frame(height: cellWidth * 3 / 2) + .frame(maxWidth: cellWidth) .cornerRadius(10) .clipped() .overlay( diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 8b47f57..2ad0986 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -65,9 +65,10 @@ struct MediaInfoView: View { .shimmering() } .resizable() - .aspectRatio(2/3, contentMode: .fit) - .cornerRadius(10) + .aspectRatio(contentMode: .fill) .frame(width: 150, height: 225) + .clipped() + .cornerRadius(10) VStack(alignment: .leading, spacing: 4) { Text(title) diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView.swift index de0bb1b..9680cca 100644 --- a/Sora/Views/SearchView.swift +++ b/Sora/Views/SearchView.swift @@ -112,15 +112,21 @@ struct SearchView: View { .frame(maxWidth: .infinity) .padding(.top) } else { + let totalSpacing: CGFloat = 16 * CGFloat(columnsCount + 1) // Spacing between items + let availableWidth = UIScreen.main.bounds.width - totalSpacing + let cellWidth = availableWidth / CGFloat(columnsCount) + LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) { ForEach(searchItems) { item in NavigationLink(destination: MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: selectedModule!)) { VStack { KFImage(URL(string: item.imageUrl)) .resizable() - .aspectRatio(2/3, contentMode: .fit) + .aspectRatio(contentMode: .fill) + .frame(height: cellWidth * 3 / 2) + .frame(maxWidth: cellWidth) .cornerRadius(10) - .frame(maxWidth: .infinity) + .clipped() Text(item.title) .font(.subheadline) .foregroundColor(Color.primary) From a1bbc51c118c5622a24bc016bec338d3d96488cd Mon Sep 17 00:00:00 2001 From: Ibrahim Sulejmenov Date: Sat, 15 Mar 2025 22:35:26 +0100 Subject: [PATCH 3/4] added setting to change anime per row in landscape and portrait modes + fixed stretched images 3 --- Sora/Views/SearchView.swift | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView.swift index 9680cca..85802ba 100644 --- a/Sora/Views/SearchView.swift +++ b/Sora/Views/SearchView.swift @@ -43,6 +43,26 @@ struct SearchView: View { "Please wait...", "Almost there..." ] + + private var columnsCount: Int { + if UIDevice.current.userInterfaceIdiom == .pad { + let isLandscape = UIScreen.main.bounds.width > UIScreen.main.bounds.height + return isLandscape ? mediaColumnsLandscape : mediaColumnsPortrait + } else { + return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait + } + } + + private var cellWidth: CGFloat { + let keyWindow = UIApplication.shared.connectedScenes + .compactMap { ($0 as? UIWindowScene)?.windows.first(where: { $0.isKeyWindow }) } + .first + let safeAreaInsets = keyWindow?.safeAreaInsets ?? .zero + let safeWidth = UIScreen.main.bounds.width - safeAreaInsets.left - safeAreaInsets.right + let totalSpacing: CGFloat = 16 * CGFloat(columnsCount + 1) + let availableWidth = safeWidth - totalSpacing + return availableWidth / CGFloat(columnsCount) + } var body: some View { NavigationView { @@ -86,10 +106,6 @@ struct SearchView: View { if !searchText.isEmpty { if isSearching { - let totalSpacing: CGFloat = 16 * CGFloat(columnsCount + 1) // Spacing between items - let availableWidth = UIScreen.main.bounds.width - totalSpacing - let cellWidth = availableWidth / CGFloat(columnsCount) - LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) { ForEach(0.. Date: Sat, 15 Mar 2025 23:07:43 +0100 Subject: [PATCH 4/4] added setting to change anime per row in landscape and portrait modes + fixed stretched images 4 --- Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift | 1 + Sora/Utils/SkeletonCells/SkeletonCell.swift | 4 ++-- Sora/Views/LibraryView/LibraryView.swift | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index b783d4d..9db9b58 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -800,3 +800,4 @@ class CustomMediaPlayerViewController: UIViewController { // yes? Like the plural of the famous american rapper ye? -IBHRAD // low taper fade the meme is massive -cranci // cranci still doesnt have a job -seiike +// guys watch Clannad already - ibro diff --git a/Sora/Utils/SkeletonCells/SkeletonCell.swift b/Sora/Utils/SkeletonCells/SkeletonCell.swift index deb6c67..342b2bf 100644 --- a/Sora/Utils/SkeletonCells/SkeletonCell.swift +++ b/Sora/Utils/SkeletonCells/SkeletonCell.swift @@ -14,7 +14,7 @@ struct HomeSkeletonCell: View { VStack { RoundedRectangle(cornerRadius: 10) .fill(Color.gray.opacity(0.3)) - .frame(width: cellWidth, height: cellWidth * 1.5) // Maintains 2:3 aspect ratio + .frame(width: cellWidth, height: cellWidth * 1.5) .cornerRadius(10) .shimmering() @@ -34,7 +34,7 @@ struct SearchSkeletonCell: View { VStack(alignment: .leading, spacing: 8) { RoundedRectangle(cornerRadius: 10) .fill(Color.gray.opacity(0.3)) - .frame(width: cellWidth, height: cellWidth * 1.5) // Maintains 2:3 aspect ratio + .frame(width: cellWidth, height: cellWidth * 1.5) .shimmering() RoundedRectangle(cornerRadius: 5) .fill(Color.gray.opacity(0.3)) diff --git a/Sora/Views/LibraryView/LibraryView.swift b/Sora/Views/LibraryView/LibraryView.swift index 396300c..1e5612f 100644 --- a/Sora/Views/LibraryView/LibraryView.swift +++ b/Sora/Views/LibraryView/LibraryView.swift @@ -76,7 +76,7 @@ struct LibraryView: View { .frame(maxWidth: .infinity) } else { LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 12), count: columnsCount), spacing: 12) { - let totalSpacing: CGFloat = 16 * CGFloat(columnsCount + 1) // Spacing between items + let totalSpacing: CGFloat = 16 * CGFloat(columnsCount + 1) let availableWidth = UIScreen.main.bounds.width - totalSpacing let cellWidth = availableWidth / CGFloat(columnsCount)