Added setting to change anime per row in landscape and portrait modes + fixed stretched images (#42)
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled

This commit is contained in:
cranci 2025-03-16 06:33:11 +01:00 committed by GitHub
commit 243f85702d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 148 additions and 20 deletions

View file

@ -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

View file

@ -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)
.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)
.shimmering()
RoundedRectangle(cornerRadius: 5)
.fill(Color.gray.opacity(0.3))
.frame(width: 150, height: 20)
.frame(width: cellWidth, height: 20)
.shimmering()
}
}

View file

@ -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,27 @@ 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) {
let totalSpacing: CGFloat = 16 * CGFloat(columnsCount + 1)
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)) {
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)
.aspectRatio(contentMode: .fill)
.frame(height: cellWidth * 3 / 2)
.frame(maxWidth: cellWidth)
.cornerRadius(10)
.clipped()
.overlay(
@ -94,11 +107,10 @@ struct LibraryView: View {
alignment: .topLeading
)
}
Text(item.title)
.font(.subheadline)
.foregroundColor(.primary)
.lineLimit(2)
.lineLimit(1)
.multilineTextAlignment(.leading)
}
}
@ -106,6 +118,12 @@ struct LibraryView: View {
}
}
.padding(.horizontal, 20)
.onAppear {
updateOrientation()
}
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
updateOrientation()
}
}
}
.padding(.vertical, 20)
@ -135,6 +153,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 {

View file

@ -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)

View file

@ -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 }
@ -39,9 +44,31 @@ struct SearchView: View {
"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 {
ScrollView {
let columnsCount = determineColumns()
VStack(spacing: 0) {
HStack {
SearchBar(text: $searchText, onSearchButtonClicked: performSearch)
@ -79,9 +106,9 @@ struct SearchView: View {
if !searchText.isEmpty {
if isSearching {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 150))], spacing: 16) {
ForEach(0..<2, id: \.self) { _ in
SearchSkeletonCell()
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
ForEach(0..<columnsCount*4, id: \.self) { _ in
SearchSkeletonCell(cellWidth: cellWidth)
}
}
.padding(.top)
@ -101,16 +128,17 @@ struct SearchView: View {
.frame(maxWidth: .infinity)
.padding(.top)
} else {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 150))], spacing: 16) {
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(width: 150, height: 225)
.clipped()
Text(item.title)
.font(.subheadline)
.foregroundColor(Color.primary)
@ -119,6 +147,12 @@ struct SearchView: View {
}
}
}
.onAppear {
updateOrientation()
}
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
updateOrientation()
}
}
.padding(.top)
.padding()
@ -219,6 +253,20 @@ struct SearchView: View {
}
}
}
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 SearchBar: View {

View file

@ -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)