mirror of
https://github.com/cranci1/Sora.git
synced 2026-03-11 17:45:37 +00:00
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
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
This commit is contained in:
commit
243f85702d
6 changed files with 148 additions and 20 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue