migrated to NukeUI from KingFisher

This commit is contained in:
Francesco 2025-06-08 21:28:02 +02:00
parent 1692ac2c3d
commit fa758159dc
15 changed files with 502 additions and 365 deletions

View file

@ -5,8 +5,8 @@
// Created by Francesco on 01/02/25.
//
import NukeUI
import SwiftUI
import Kingfisher
struct ModuleAdditionSettingsView: View {
@Environment(\.presentationMode) var presentationMode
@ -22,7 +22,7 @@ struct ModuleAdditionSettingsView: View {
ZStack {
LinearGradient(
gradient: Gradient(colors: [
colorScheme == .dark ? Color.black : Color.white,
colorScheme == .light ? Color.black : Color.white,
Color.accentColor.opacity(0.08)
]),
startPoint: .top,
@ -45,17 +45,24 @@ struct ModuleAdditionSettingsView: View {
VStack(spacing: 24) {
if let metadata = moduleMetadata {
VStack(spacing: 0) {
KFImage(URL(string: metadata.iconUrl))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 90, height: 90)
.clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
.shadow(color: Color.accentColor.opacity(0.18), radius: 10, x: 0, y: 6)
.overlay(
RoundedRectangle(cornerRadius: 22)
.stroke(Color.accentColor, lineWidth: 2)
)
.padding(.top, 10)
LazyImage(source: URL(string: metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
} else {
Rectangle()
.fill(Color(.systemGray5))
}
}
.frame(width: 90, height: 90)
.clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
.shadow(color: Color.accentColor.opacity(0.18), radius: 10, x: 0, y: 6)
.overlay(
RoundedRectangle(cornerRadius: 22)
.stroke(Color.accentColor, lineWidth: 2)
)
.padding(.top, 10)
VStack(spacing: 6) {
Text(metadata.sourceName)
@ -65,12 +72,19 @@ struct ModuleAdditionSettingsView: View {
.padding(.top, 6)
HStack(spacing: 10) {
KFImage(URL(string: metadata.author.icon))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 32, height: 32)
.clipShape(Circle())
.shadow(radius: 2)
LazyImage(source: URL(string: metadata.author.icon)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
} else {
Circle()
.fill(Color(.systemGray5))
}
}
.frame(width: 32, height: 32)
.clipShape(Circle())
.shadow(radius: 2)
VStack(alignment: .leading, spacing: 0) {
Text(metadata.author.name)
.font(.headline)
@ -166,7 +180,7 @@ struct ModuleAdditionSettingsView: View {
Text("Add Module")
}
.font(.headline)
.foregroundColor(colorScheme == .dark ? .black : .white)
.foregroundColor(colorScheme == .light ? .black : .white)
.frame(maxWidth: .infinity)
.padding(.vertical, 14)
.background(

View file

@ -5,9 +5,9 @@
// Created by doomsboygaming on 5/22/25
//
import SwiftUI
import AVKit
import Kingfisher
import NukeUI
import SwiftUI
struct DownloadView: View {
@EnvironmentObject var jsController: JSController
@ -741,13 +741,16 @@ struct EnhancedActiveDownloadCard: View {
HStack(spacing: 16) {
Group {
if let imageURL = download.imageURL {
KFImage(imageURL)
.placeholder {
LazyImage(source: imageURL) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
} else {
Rectangle()
.fill(.tertiary)
}
.resizable()
.aspectRatio(contentMode: .fill)
}
} else {
Rectangle()
.fill(.tertiary)
@ -899,16 +902,18 @@ struct EnhancedDownloadGroupCard: View {
NavigationLink(destination: EnhancedShowEpisodesView(group: group, onDelete: onDelete, onPlay: onPlay)) {
VStack(spacing: 0) {
HStack(spacing: 16) {
// Poster
Group {
if let posterURL = group.posterURL {
KFImage(posterURL)
.placeholder {
LazyImage(source: posterURL) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
} else {
Rectangle()
.fill(.tertiary)
}
.resizable()
.aspectRatio(contentMode: .fill)
}
} else {
Rectangle()
.fill(.tertiary)
@ -921,7 +926,6 @@ struct EnhancedDownloadGroupCard: View {
.frame(width: 56, height: 84)
.clipShape(RoundedRectangle(cornerRadius: 8))
// Content
VStack(alignment: .leading, spacing: 8) {
Text(group.title)
.font(.headline)
@ -1000,18 +1004,20 @@ struct EnhancedShowEpisodesView: View {
var body: some View {
ScrollView {
VStack(spacing: 24) {
// Header Section
VStack(spacing: 20) {
HStack(alignment: .top, spacing: 20) {
Group {
if let posterURL = group.posterURL {
KFImage(posterURL)
.placeholder {
LazyImage(source: posterURL) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
} else {
Rectangle()
.fill(.tertiary)
}
.resizable()
.aspectRatio(contentMode: .fill)
}
} else {
Rectangle()
.fill(.tertiary)
@ -1192,16 +1198,18 @@ struct EnhancedEpisodeRow: View {
var body: some View {
VStack(spacing: 0) {
HStack(spacing: 16) {
// Thumbnail
Group {
if let backdropURL = asset.metadata?.backdropURL ?? asset.metadata?.posterURL {
KFImage(backdropURL)
.placeholder {
LazyImage(source: backdropURL) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
} else {
Rectangle()
.fill(.tertiary)
}
.resizable()
.aspectRatio(contentMode: .fill)
}
} else {
Rectangle()
.fill(.tertiary)
@ -1214,7 +1222,6 @@ struct EnhancedEpisodeRow: View {
.frame(width: 100, height: 60)
.clipShape(RoundedRectangle(cornerRadius: 8))
// Content
VStack(alignment: .leading, spacing: 4) {
Text(asset.episodeDisplayName)
.font(.headline)

View file

@ -5,9 +5,9 @@
// Created by paul on 29/04/2025.
//
import SwiftUI
import Kingfisher
import UIKit
import NukeUI
import SwiftUI
extension View {
func circularGradientOutlineTwo() -> some View {
@ -59,28 +59,44 @@ struct BookmarkCell: View {
var body: some View {
if let module = moduleManager.modules.first(where: { $0.id.uuidString == bookmark.moduleId }) {
ZStack {
KFImage(URL(string: bookmark.imageUrl))
.resizable()
.aspectRatio(0.72, contentMode: .fill)
.frame(width: 162, height: 243)
.cornerRadius(12)
.clipped()
.overlay(
ZStack {
Circle()
.fill(Color.black.opacity(0.5))
.frame(width: 28, height: 28)
.overlay(
KFImage(URL(string: module.metadata.iconUrl))
.resizable()
.scaledToFill()
.frame(width: 32, height: 32)
.clipShape(Circle())
)
}
.padding(8),
alignment: .topLeading
)
LazyImage(source: URL(string: bookmark.imageUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(0.72, contentMode: .fill)
.frame(width: 162, height: 243)
.cornerRadius(12)
.clipped()
} else {
RoundedRectangle(cornerRadius: 12)
.fill(Color.gray.opacity(0.3))
.frame(width: 162, height: 243)
}
}
.overlay(
ZStack {
Circle()
.fill(Color.black.opacity(0.5))
.frame(width: 28, height: 28)
.overlay(
LazyImage(source: URL(string: module.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.scaledToFill()
.frame(width: 32, height: 32)
.clipShape(Circle())
} else {
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 32, height: 32)
}
}
)
}
.padding(8),
alignment: .topLeading
)
VStack {
Spacer()

View file

@ -5,9 +5,9 @@
// Created by paul on 24/05/2025.
//
import SwiftUI
import Kingfisher
import UIKit
import NukeUI
import SwiftUI
extension View {
func circularGradientOutline() -> some View {
@ -206,75 +206,86 @@ struct FullWidthContinueWatchingCell: View {
}) {
GeometryReader { geometry in
ZStack(alignment: .bottomLeading) {
KFImage(URL(string: item.imageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : item.imageUrl))
.placeholder {
LazyImage(source: URL(string: item.imageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : item.imageUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geometry.size.width, height: 157.03)
.cornerRadius(10)
.clipped()
} else {
RoundedRectangle(cornerRadius: 10)
.fill(Color.gray.opacity(0.3))
.frame(height: 157.03)
.shimmering()
}
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geometry.size.width, height: 157.03)
.cornerRadius(10)
.clipped()
.overlay(
ZStack {
ProgressiveBlurView()
.cornerRadius(10, corners: [.bottomLeft, .bottomRight])
}
.overlay(
ZStack {
ProgressiveBlurView()
.cornerRadius(10, corners: [.bottomLeft, .bottomRight])
VStack(alignment: .leading, spacing: 4) {
Spacer()
Text(item.mediaTitle)
.font(.headline)
.foregroundColor(.white)
.lineLimit(1)
HStack {
Text("Episode \(item.episodeNumber)")
.font(.subheadline)
.foregroundColor(.white.opacity(0.9))
VStack(alignment: .leading, spacing: 4) {
Spacer()
Text(item.mediaTitle)
.font(.headline)
.foregroundColor(.white)
.lineLimit(1)
HStack {
Text("Episode \(item.episodeNumber)")
.font(.subheadline)
.foregroundColor(.white.opacity(0.9))
Spacer()
Text("\(Int(item.progress * 100))% seen")
.font(.caption)
.foregroundColor(.white.opacity(0.9))
}
Text("\(Int(item.progress * 100))% seen")
.font(.caption)
.foregroundColor(.white.opacity(0.9))
}
.padding(10)
.background(
LinearGradient(
colors: [
.black.opacity(0.7),
.black.opacity(0.0)
],
startPoint: .bottom,
endPoint: .top
)
}
.padding(10)
.background(
LinearGradient(
colors: [
.black.opacity(0.7),
.black.opacity(0.0)
],
startPoint: .bottom,
endPoint: .top
)
.clipped()
.cornerRadius(10, corners: [.bottomLeft, .bottomRight])
.shadow(color: .black.opacity(0.3), radius: 3, x: 0, y: 1)
)
},
alignment: .bottom
)
.overlay(
ZStack {
Circle()
.fill(Color.black.opacity(0.5))
.frame(width: 28, height: 28)
.overlay(
LazyImage(source: URL(string: item.module.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.scaledToFill()
.frame(width: 32, height: 32)
.clipShape(Circle())
} else {
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 32, height: 32)
}
}
)
},
alignment: .bottom
)
.overlay(
ZStack {
Circle()
.fill(Color.black.opacity(0.5))
.frame(width: 28, height: 28)
.overlay(
KFImage(URL(string: item.module.metadata.iconUrl))
.resizable()
.scaledToFill()
.frame(width: 32, height: 32)
.clipShape(Circle())
)
}
}
.padding(8),
alignment: .topLeading
)
alignment: .topLeading
)
}
}
.frame(height: 157.03)

View file

@ -5,9 +5,9 @@
// Created by Francesco on 05/01/25.
//
import SwiftUI
import Kingfisher
import UIKit
import NukeUI
import SwiftUI
struct LibraryView: View {
@EnvironmentObject private var libraryManager: LibraryManager
@ -280,85 +280,96 @@ struct ContinueWatchingCell: View {
}
}) {
ZStack(alignment: .bottomLeading) {
KFImage(URL(string: item.imageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : item.imageUrl))
.placeholder {
LazyImage(source: URL(string: item.imageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : item.imageUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(16/9, contentMode: .fill)
.frame(width: 280, height: 157.03)
.cornerRadius(10)
.clipped()
} else {
RoundedRectangle(cornerRadius: 10)
.fill(Color.gray.opacity(0.3))
.frame(width: 280, height: 157.03)
.shimmering()
}
.resizable()
.aspectRatio(16/9, contentMode: .fill)
.frame(width: 280, height: 157.03)
.cornerRadius(10)
.clipped()
.overlay(
ZStack {
ProgressiveBlurView()
.cornerRadius(10, corners: [.bottomLeft, .bottomRight])
}
.overlay(
ZStack {
ProgressiveBlurView()
.cornerRadius(10, corners: [.bottomLeft, .bottomRight])
VStack(alignment: .leading, spacing: 4) {
Spacer()
Text(item.mediaTitle)
.font(.headline)
.foregroundColor(.white)
.lineLimit(1)
HStack {
Text("Episode \(item.episodeNumber)")
.font(.subheadline)
.foregroundColor(.white.opacity(0.9))
VStack(alignment: .leading, spacing: 4) {
Spacer()
Text(item.mediaTitle)
.font(.headline)
.foregroundColor(.white)
.lineLimit(1)
HStack {
Text("Episode \(item.episodeNumber)")
.font(.subheadline)
.foregroundColor(.white.opacity(0.9))
Spacer()
Text("\(Int(item.progress * 100))% seen")
.font(.caption)
.foregroundColor(.white.opacity(0.9))
}
Text("\(Int(item.progress * 100))% seen")
.font(.caption)
.foregroundColor(.white.opacity(0.9))
}
.padding(10)
.background(
LinearGradient(
colors: [
.black.opacity(0.7),
.black.opacity(0.0)
],
startPoint: .bottom,
endPoint: .top
)
.clipped()
.cornerRadius(10, corners: [.bottomLeft, .bottomRight])
.shadow(color: .black.opacity(0.3), radius: 3, x: 0, y: 1)
}
.padding(10)
.background(
LinearGradient(
colors: [
.black.opacity(0.7),
.black.opacity(0.0)
],
startPoint: .bottom,
endPoint: .top
)
},
alignment: .bottom
)
.overlay(
ZStack {
if item.streamUrl.hasPrefix("file://") {
Image(systemName: "arrow.down.app.fill")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.foregroundColor(.white)
.background(Color.black.cornerRadius(6))
.padding(8)
} else {
Circle()
.fill(Color.black.opacity(0.5))
.frame(width: 28, height: 28)
.overlay(
KFImage(URL(string: item.module.metadata.iconUrl))
.resizable()
.scaledToFill()
.frame(width: 32, height: 32)
.clipShape(Circle())
)
.padding(8)
}
},
alignment: .topLeading
)
.clipped()
.cornerRadius(10, corners: [.bottomLeft, .bottomRight])
.shadow(color: .black.opacity(0.3), radius: 3, x: 0, y: 1)
)
},
alignment: .bottom
)
.overlay(
ZStack {
if item.streamUrl.hasPrefix("file://") {
Image(systemName: "arrow.down.app.fill")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.foregroundColor(.white)
.background(Color.black.cornerRadius(6))
.padding(8)
} else {
Circle()
.fill(Color.black.opacity(0.5))
.frame(width: 28, height: 28)
.overlay(
LazyImage(source: URL(string: item.module.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.scaledToFill()
.frame(width: 32, height: 32)
.clipShape(Circle())
} else {
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 32, height: 32)
}
}
)
.padding(8)
}
},
alignment: .topLeading
)
}
.frame(width: 280, height: 157.03)
}
@ -525,34 +536,45 @@ struct BookmarkItemView: View {
isDetailActive = true
}) {
ZStack {
KFImage(URL(string: item.imageUrl))
.placeholder {
LazyImage(source: URL(string: item.imageUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(0.72, contentMode: .fill)
.frame(width: 162, height: 243)
.cornerRadius(12)
.clipped()
} else {
RoundedRectangle(cornerRadius: 12)
.fill(Color.gray.opacity(0.3))
.aspectRatio(2 / 3, contentMode: .fit)
.shimmering()
}
.resizable()
.aspectRatio(0.72, contentMode: .fill)
.frame(width: 162, height: 243)
.cornerRadius(12)
.clipped()
.overlay(
ZStack {
Circle()
.fill(Color.black.opacity(0.5))
.frame(width: 28, height: 28)
.overlay(
KFImage(URL(string: module.metadata.iconUrl))
.resizable()
.scaledToFill()
.frame(width: 32, height: 32)
.clipShape(Circle())
)
}
.padding(8),
alignment: .topLeading
)
}
.overlay(
ZStack {
Circle()
.fill(Color.black.opacity(0.5))
.frame(width: 28, height: 28)
.overlay(
LazyImage(source: URL(string: module.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.scaledToFill()
.frame(width: 32, height: 32)
.clipShape(Circle())
} else {
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 32, height: 32)
}
}
)
}
.padding(8),
alignment: .topLeading
)
VStack {
Spacer()

View file

@ -5,8 +5,8 @@
// Created by seiike on 01/06/2025.
//
import NukeUI
import SwiftUI
import Kingfisher
struct AnilistMatchPopupView: View {
let seriesTitle: String
@ -32,7 +32,6 @@ struct AnilistMatchPopupView: View {
NavigationView {
ScrollView {
VStack(alignment: .leading, spacing: 4) {
// (Optional) A hidden header; can be omitted if empty
Text("".uppercased())
.font(.footnote)
.foregroundStyle(.gray)
@ -62,11 +61,20 @@ struct AnilistMatchPopupView: View {
HStack(spacing: 12) {
if let cover = result["cover"] as? String,
let url = URL(string: cover) {
KFImage(url)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 50, height: 70)
.cornerRadius(6)
LazyImage(source: url) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 50, height: 70)
.cornerRadius(6)
} else {
Rectangle()
.fill(.tertiary)
.frame(width: 50, height: 70)
.cornerRadius(6)
}
}
}
VStack(alignment: .leading, spacing: 2) {

View file

@ -5,8 +5,8 @@
// Created by Francesco on 18/12/24.
//
import NukeUI
import SwiftUI
import Kingfisher
import AVFoundation
struct EpisodeCell: View {
@ -264,14 +264,28 @@ struct EpisodeCell: View {
private var episodeThumbnail: some View {
ZStack {
if let url = URL(string: episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl) {
KFImage(url)
.onFailure { error in
Logger.shared.log("Failed to load episode image: \(error)", type: "Error")
LazyImage(source: url) { state in
if let image = state.imageContainer?.image {
Image(uiImage: image)
.resizable()
.aspectRatio(16/9, contentMode: .fill)
.frame(width: 100, height: 56)
.cornerRadius(8)
} else if state.error != nil {
Rectangle()
.fill(.tertiary)
.frame(width: 100, height: 56)
.cornerRadius(8)
.onAppear {
Logger.shared.log("Failed to load episode image: \(state.error?.localizedDescription ?? "Unknown error")", type: "Error")
}
} else {
Rectangle()
.fill(.tertiary)
.frame(width: 100, height: 56)
.cornerRadius(8)
}
.resizable()
.aspectRatio(16/9, contentMode: .fill)
.frame(width: 100, height: 56)
.cornerRadius(8)
}
} else {
Rectangle()
.fill(Color.gray.opacity(0.3))

View file

@ -5,8 +5,8 @@
// Created by Francesco on 05/01/25.
//
import NukeUI
import SwiftUI
import Kingfisher
import SafariServices
private let tmdbFetcher = TMDBFetcher()
@ -212,46 +212,21 @@ struct MediaInfoView: View {
private var mainScrollView: some View {
ScrollView {
ZStack(alignment: .top) {
KFImage(URL(string: imageUrl))
.placeholder {
LazyImage(source: URL(string: imageUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: UIScreen.main.bounds.width, height: 700)
.clipped()
} else {
Rectangle()
.fill(Color.gray.opacity(0.3))
.shimmering()
.frame(width: UIScreen.main.bounds.width, height: 700)
.clipped()
}
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: UIScreen.main.bounds.width, height: 700)
.clipped()
KFImage(URL(string: imageUrl))
.placeholder { EmptyView() }
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: UIScreen.main.bounds.width, height: 700)
.clipped()
.blur(radius: 30)
.mask(
LinearGradient(
gradient: Gradient(stops: [
.init(color: .clear, location: 0.0),
.init(color: .clear, location: 0.6),
.init(color: .black, location: 0.8),
.init(color: .black, location: 1.0)
]),
startPoint: .top,
endPoint: .bottom
)
)
.overlay(
LinearGradient(
gradient: Gradient(stops: [
.init(color: .clear, location: 0.0),
.init(color: .clear, location: 0.7),
.init(color: (colorScheme == .dark ? Color.black : Color.white).opacity(0.9), location: 1.0)
]),
startPoint: .top,
endPoint: .bottom
)
)
}
VStack(spacing: 0) {
Rectangle()
.fill(Color.clear)

View file

@ -5,8 +5,8 @@
// Created by paul on 28/05/25.
//
import NukeUI
import SwiftUI
import Kingfisher
struct SearchResultsGrid: View {
@AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2
@ -32,12 +32,22 @@ struct SearchResultsGrid: View {
ForEach(items) { item in
NavigationLink(destination: MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: selectedModule)) {
ZStack {
KFImage(URL(string: item.imageUrl))
.resizable()
.aspectRatio(0.72, contentMode: .fill)
.frame(width: cellWidth, height: cellWidth * 1.5)
.cornerRadius(12)
.clipped()
LazyImage(source: URL(string: item.imageUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(0.72, contentMode: .fill)
.frame(width: cellWidth, height: cellWidth * 1.5)
.cornerRadius(12)
.clipped()
} else {
Rectangle()
.fill(.tertiary)
.frame(width: cellWidth, height: cellWidth * 1.5)
.cornerRadius(12)
.clipped()
}
}
VStack {
Spacer()

View file

@ -5,8 +5,8 @@
// Created by Francesco on 27/01/25.
//
import NukeUI
import SwiftUI
import Kingfisher
struct ModuleSelectorMenu: View {
let selectedModule: ScrapingModule?
@ -27,11 +27,19 @@ struct ModuleSelectorMenu: View {
onModuleSelected(module.id.uuidString)
} label: {
HStack {
KFImage(URL(string: module.metadata.iconUrl))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20)
.cornerRadius(4)
LazyImage(source: URL(string: module.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20)
.cornerRadius(4)
} else {
Circle()
.fill(Color(.systemGray5))
}
}
Text(module.metadata.sourceName)
if module.id.uuidString == selectedModuleId {
Image(systemName: "checkmark")
@ -48,29 +56,37 @@ struct ModuleSelectorMenu: View {
Text(selectedModule.metadata.sourceName)
.font(.headline)
.foregroundColor(.primary)
KFImage(URL(string: selectedModule.metadata.iconUrl))
.resizable()
.frame(width: 36, height: 36)
.clipShape(Circle())
.background(
LazyImage(source: URL(string: selectedModule.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.frame(width: 36, height: 36)
.clipShape(Circle())
} else {
Circle()
.fill(.ultraThinMaterial)
.overlay(
Circle()
.stroke(
LinearGradient(
gradient: Gradient(stops: [
.init(color: Color.accentColor.opacity(gradientOpacity), location: 0),
.init(color: Color.accentColor.opacity(0), location: 1)
]),
startPoint: .top,
endPoint: .bottom
),
lineWidth: 0.5
)
)
.matchedGeometryEffect(id: "background_circle", in: animation)
)
.frame(width: 36, height: 36)
}
}
.background(
Circle()
.fill(.ultraThinMaterial)
.overlay(
Circle()
.stroke(
LinearGradient(
gradient: Gradient(stops: [
.init(color: Color.accentColor.opacity(gradientOpacity), location: 0),
.init(color: Color.accentColor.opacity(0), location: 1)
]),
startPoint: .top,
endPoint: .bottom
),
lineWidth: 0.5
)
)
.matchedGeometryEffect(id: "background_circle", in: animation)
)
} else {
Text("Select Module")
.font(.headline)

View file

@ -5,8 +5,8 @@
// Created by Francesco on 26/05/25.
//
import NukeUI
import SwiftUI
import Kingfisher
fileprivate struct SettingsSection<Content: View>: View {
let title: String
@ -66,14 +66,18 @@ struct SettingsViewAbout: View {
VStack(spacing: 24) {
SettingsSection(title: "App Info", footer: "Sora/Sulfur will always remain free with no ADs!") {
HStack(alignment: .center, spacing: 16) {
KFImage(URL(string: "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/dev/Sora/Assets.xcassets/AppIcons/AppIcon_Default.appiconset/darkmode.png"))
.placeholder {
LazyImage(source: URL(string: "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/dev/Sora/Assets.xcassets/AppIcons/AppIcon_Default.appiconset/darkmode.png")) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.frame(width: 100, height: 100)
.cornerRadius(20)
.shadow(radius: 5)
} else {
ProgressView()
.frame(width: 40, height: 40)
}
.resizable()
.frame(width: 100, height: 100)
.cornerRadius(20)
.shadow(radius: 5)
}
VStack(alignment: .leading, spacing: 8) {
Text("Sora")
@ -96,13 +100,17 @@ struct SettingsViewAbout: View {
}
}) {
HStack {
KFImage(URL(string: "https://avatars.githubusercontent.com/u/100066266?v=4"))
.placeholder {
LazyImage(source: URL(string: "https://avatars.githubusercontent.com/u/100066266?v=4")) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.frame(width: 40, height: 40)
.clipShape(Circle())
} else {
ProgressView()
.frame(width: 40, height: 40)
}
.resizable()
.frame(width: 40, height: 40)
.clipShape(Circle())
}
VStack(alignment: .leading) {
Text("cranci1")
@ -205,13 +213,17 @@ struct ContributorView: View {
}
}) {
HStack {
KFImage(URL(string: contributor.avatarUrl))
.placeholder {
LazyImage(source: URL(string: contributor.avatarUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.frame(width: 40, height: 40)
.clipShape(Circle())
} else {
ProgressView()
.frame(width: 40, height: 40)
}
.resizable()
.frame(width: 40, height: 40)
.clipShape(Circle())
}
Text(contributor.login)
.font(.headline)

View file

@ -5,8 +5,8 @@
// Created by Francesco on 05/01/25.
//
import NukeUI
import SwiftUI
import Kingfisher
fileprivate struct SettingsSection<Content: View>: View {
let title: String
@ -67,11 +67,19 @@ fileprivate struct ModuleListItemView: View {
var body: some View {
VStack(spacing: 0) {
HStack {
KFImage(URL(string: module.metadata.iconUrl))
.resizable()
.frame(width: 40, height: 40)
.clipShape(Circle())
.padding(.trailing, 10)
LazyImage(source: URL(string: module.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.frame(width: 40, height: 40)
.clipShape(Circle())
.padding(.trailing, 10)
} else {
Circle()
.frame(width: 40, height: 40)
.padding(.trailing, 10)
}
}
VStack(alignment: .leading, spacing: 2) {
HStack(alignment: .bottom, spacing: 4) {

View file

@ -5,9 +5,9 @@
// Created by Francesco on 23/03/25.
//
import NukeUI
import SwiftUI
import Security
import Kingfisher
fileprivate struct SettingsSection<Content: View>: View {
let title: String
@ -120,18 +120,21 @@ struct SettingsViewTrackers: View {
SettingsSection(title: "AniList") {
VStack(spacing: 0) {
HStack(alignment: .center, spacing: 10) {
KFImage(URL(string: "https://raw.githubusercontent.com/cranci1/Ryu/2f10226aa087154974a70c1ec78aa83a47daced9/Ryu/Assets.xcassets/Listing/Anilist.imageset/anilist.png"))
.placeholder {
LazyImage(source: URL(string: "https://raw.githubusercontent.com/cranci1/Ryu/2f10226aa087154974a70c1ec78aa83a47daced9/Ryu/Assets.xcassets/Listing/Anilist.imageset/anilist.png")) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.frame(width: 60, height: 60)
.clipShape(Rectangle())
.cornerRadius(10)
.padding(.trailing, 10)
} else {
RoundedRectangle(cornerRadius: 10)
.fill(Color.gray.opacity(0.3))
.frame(width: 60, height: 60)
.shimmering()
}
.resizable()
.frame(width: 60, height: 60)
.clipShape(Rectangle())
.cornerRadius(10)
.padding(.trailing, 10)
}
VStack(alignment: .leading, spacing: 4) {
Text("AniList.co")
@ -212,18 +215,21 @@ struct SettingsViewTrackers: View {
SettingsSection(title: "Trakt") {
VStack(spacing: 0) {
HStack(alignment: .center, spacing: 10) {
KFImage(URL(string: "https://static-00.iconduck.com/assets.00/trakt-icon-2048x2048-2633ksxg.png"))
.placeholder {
LazyImage(source: URL(string: "https://static-00.iconduck.com/assets.00/trakt-icon-2048x2048-2633ksxg.png")) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
.frame(width: 60, height: 60)
.clipShape(Rectangle())
.cornerRadius(10)
.padding(.trailing, 10)
} else {
RoundedRectangle(cornerRadius: 10)
.fill(Color.gray.opacity(0.3))
.frame(width: 60, height: 60)
.shimmering()
}
.resizable()
.frame(width: 60, height: 60)
.clipShape(Rectangle())
.cornerRadius(10)
.padding(.trailing, 10)
}
VStack(alignment: .leading, spacing: 4) {
Text("Trakt.tv")

View file

@ -48,7 +48,6 @@
1359ED142D76F49900C13034 /* finTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1359ED132D76F49900C13034 /* finTopView.swift */; };
135CCBE22D4D1138008B9C0E /* SettingsViewPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */; };
13637B8A2DE0EA1100BDA2FC /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13637B892DE0EA1100BDA2FC /* UserDefaults.swift */; };
13637B8D2DE0ECCC00BDA2FC /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 13637B8C2DE0ECCC00BDA2FC /* Kingfisher */; };
13637B902DE0ECD200BDA2FC /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = 13637B8F2DE0ECD200BDA2FC /* Drops */; };
13637B932DE0ECDB00BDA2FC /* MarqueeLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 13637B922DE0ECDB00BDA2FC /* MarqueeLabel */; };
136BBE802DB1038000906B5E /* Notification+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136BBE7F2DB1038000906B5E /* Notification+Name.swift */; };
@ -61,6 +60,7 @@
1399FAD62D3AB3DB00E97C31 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1399FAD52D3AB3DB00E97C31 /* Logger.swift */; };
13B77E202DA457AA00126FDF /* AniListPushUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B77E1F2DA457AA00126FDF /* AniListPushUpdates.swift */; };
13B7F4C12D58FFDD0045714A /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B7F4C02D58FFDD0045714A /* Shimmer.swift */; };
13BC689F2DF61327009A0651 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = 13BC689E2DF61327009A0651 /* NukeUI */; };
13C0E5EA2D5F85EA00E7F619 /* ContinueWatchingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C0E5E92D5F85EA00E7F619 /* ContinueWatchingManager.swift */; };
13C0E5EC2D5F85F800E7F619 /* ContinueWatchingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C0E5EB2D5F85F800E7F619 /* ContinueWatchingItem.swift */; };
13CBA0882D60F19C00EFE70A /* VTTSubtitlesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CBA0872D60F19C00EFE70A /* VTTSubtitlesLoader.swift */; };
@ -191,9 +191,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
13637B8D2DE0ECCC00BDA2FC /* Kingfisher in Frameworks */,
13637B902DE0ECD200BDA2FC /* Drops in Frameworks */,
13637B932DE0ECDB00BDA2FC /* MarqueeLabel in Frameworks */,
13BC689F2DF61327009A0651 /* NukeUI in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -614,9 +614,9 @@
);
name = Sulfur;
packageProductDependencies = (
13637B8C2DE0ECCC00BDA2FC /* Kingfisher */,
13637B8F2DE0ECD200BDA2FC /* Drops */,
13637B922DE0ECDB00BDA2FC /* MarqueeLabel */,
13BC689E2DF61327009A0651 /* NukeUI */,
);
productName = Sora;
productReference = 133D7C6A2D2BE2500075467E /* Sulfur.app */;
@ -646,9 +646,9 @@
);
mainGroup = 133D7C612D2BE2500075467E;
packageReferences = (
13637B8B2DE0ECCC00BDA2FC /* XCRemoteSwiftPackageReference "Kingfisher" */,
13637B8E2DE0ECD200BDA2FC /* XCRemoteSwiftPackageReference "Drops" */,
13637B912DE0ECDB00BDA2FC /* XCRemoteSwiftPackageReference "MarqueeLabel" */,
13BC689D2DF61327009A0651 /* XCRemoteSwiftPackageReference "NukeUI" */,
);
productRefGroup = 133D7C6B2D2BE2500075467E /* Products */;
projectDirPath = "";
@ -994,14 +994,6 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
13637B8B2DE0ECCC00BDA2FC /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher.git";
requirement = {
kind = exactVersion;
version = 7.9.1;
};
};
13637B8E2DE0ECD200BDA2FC /* XCRemoteSwiftPackageReference "Drops" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/omaralbeik/Drops.git";
@ -1018,14 +1010,17 @@
version = 4.2.1;
};
};
13BC689D2DF61327009A0651 /* XCRemoteSwiftPackageReference "NukeUI" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kean/NukeUI";
requirement = {
branch = main;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
13637B8C2DE0ECCC00BDA2FC /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = 13637B8B2DE0ECCC00BDA2FC /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = Kingfisher;
};
13637B8F2DE0ECD200BDA2FC /* Drops */ = {
isa = XCSwiftPackageProductDependency;
package = 13637B8E2DE0ECD200BDA2FC /* XCRemoteSwiftPackageReference "Drops" */;
@ -1036,6 +1031,11 @@
package = 13637B912DE0ECDB00BDA2FC /* XCRemoteSwiftPackageReference "MarqueeLabel" */;
productName = MarqueeLabel;
};
13BC689E2DF61327009A0651 /* NukeUI */ = {
isa = XCSwiftPackageProductDependency;
package = 13BC689D2DF61327009A0651 /* XCRemoteSwiftPackageReference "NukeUI" */;
productName = NukeUI;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 133D7C622D2BE2500075467E /* Project object */;

View file

@ -11,12 +11,12 @@
}
},
{
"package": "Kingfisher",
"repositoryURL": "https://github.com/onevcat/Kingfisher.git",
"package": "Gifu",
"repositoryURL": "https://github.com/kaishin/Gifu",
"state": {
"branch": null,
"revision": "b6f62758f21a8c03cd64f4009c037cfa580a256e",
"version": "7.9.1"
"revision": "82da0086dea14ca9afc9801234ad8dc4cd9e2738",
"version": "3.4.1"
}
},
{
@ -27,6 +27,24 @@
"revision": "cffb6938940d3242882e6a2f9170b7890a4729ea",
"version": "4.2.1"
}
},
{
"package": "Nuke",
"repositoryURL": "https://github.com/kean/Nuke.git",
"state": {
"branch": null,
"revision": "a002b7fd786f2df2ed4333fe73a9727499fd9d97",
"version": "10.11.2"
}
},
{
"package": "NukeUI",
"repositoryURL": "https://github.com/kean/NukeUI",
"state": {
"branch": "main",
"revision": "7338ed8ea76de18598bfafbca0cbdc74300a6b10",
"version": null
}
}
]
},