Merge branch 'dev'

This commit is contained in:
cranci1 2025-06-09 20:52:41 +02:00
commit 8c09010a9a
22 changed files with 813 additions and 266 deletions

435
Sora/Localizable.xcstrings Normal file
View file

@ -0,0 +1,435 @@
{
"sourceLanguage" : "en",
"strings" : {
"" : {
},
"%lld" : {
},
"%lld Episodes" : {
},
"%lld of %lld" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "%1$lld of %2$lld"
}
}
}
},
"%lld-%lld" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "%1$lld-%2$lld"
}
}
}
},
"%lld%%" : {
},
"%lld%% seen" : {
},
"•" : {
},
"About" : {
},
"Actively downloading media can be tracked from here." : {
},
"Add Module" : {
},
"AKA Sulfur" : {
},
"All Bookmarks" : {
},
"All Prev" : {
},
"All Watching" : {
},
"AniList ID" : {
},
"AniList Match" : {
},
"AniList.co" : {
},
"App Data" : {
},
"Are you sure you want to delete '%@'?" : {
},
"Are you sure you want to delete all %lld episodes in '%@'?" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Are you sure you want to delete all %1$lld episodes in '%2$@'?"
}
}
}
},
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." : {
},
"Are you sure you want to erase all app data? This action cannot be undone." : {
},
"Are you sure you want to remove all files in the Documents folder? This will remove all modules." : {
},
"Author" : {
},
"Bookmark items for an easier access later." : {
},
"Bookmarks" : {
},
"Cancel" : {
},
"Check out some community modules here!" : {
},
"Clear" : {
},
"Clear All Caches" : {
},
"Clear All Downloads" : {
},
"Clear Library Only" : {
},
"Clear Logs" : {
},
"Click the plus button to add a module!" : {
},
"Continue Watching" : {
},
"Copy to Clipboard" : {
},
"Copy URL" : {
},
"cranci1" : {
},
"Current Cache Size" : {
},
"DATA/LOGS" : {
},
"Delete" : {
},
"Delete All" : {
},
"Delete All Downloads" : {
},
"Delete All Episodes" : {
},
"Delete Download" : {
},
"Delete Episode" : {
},
"Download" : {
},
"Download Episode" : {
},
"Downloads" : {
},
"Enter the AniList ID for this media" : {
},
"Episode %lld" : {
},
"Episodes" : {
},
"Episodes might not be available yet or there could be an issue with the source." : {
},
"Erase" : {
},
"Erase App Data" : {
},
"Error" : {
},
"Failed to load contributors" : {
},
"Files Downloaded" : {
},
"General" : {
},
"INFOS" : {
},
"LESS" : {
},
"Library" : {
},
"Loading Episode %lld..." : {
},
"Loading logs..." : {
},
"Loading module information..." : {
},
"Loading Stream" : {
},
"Log Debug Info" : {
},
"Log Filters" : {
},
"Log In with AniList" : {
},
"Log In with Trakt" : {
},
"Log Out from AniList" : {
},
"Log Out from Trakt" : {
},
"Logged in as " : {
},
"Logs" : {
},
"MAIN" : {
},
"Mark All Previous Watched" : {
},
"Mark as Watched" : {
},
"Match with AniList" : {
},
"Matched with: %@" : {
},
"Max Concurrent Downloads" : {
},
"me frfr" : {
},
"Modules" : {
},
"MORE" : {
},
"No Active Downloads" : {
},
"No Data Available" : {
},
"No Downloads" : {
},
"No episodes available" : {
},
"No Episodes Available" : {
},
"No items to continue watching." : {
},
"No matches found" : {
},
"No Module Selected" : {
},
"No Modules" : {
},
"No Results Found" : {
},
"OK" : {
},
"Open Community Library" : {
},
"Open in AniList" : {
},
"Play" : {
},
"Player" : {
},
"Please select a module from settings" : {
},
"Queued" : {
},
"Recently watched content will appear here." : {
},
"Refresh Storage Info" : {
},
"Remove" : {
},
"Remove Documents" : {
},
"Remove from Bookmarks" : {
},
"Remove Item" : {
},
"Reset" : {
},
"Reset AniList ID" : {
},
"Reset Progress" : {
},
"Running Sora %@ - cranci1" : {
},
"Save" : {
},
"Search" : {
},
"Search downloads" : {
},
"Search for something..." : {
},
"Search..." : {
},
"Season %lld" : {
},
"Select Module" : {
},
"Set Custom AniList ID" : {
},
"Settings" : {
},
"Show More (%lld more characters)" : {
},
"Sora" : {
},
"Sort" : {
},
"Storage Used" : {
},
"Tap a title to override the current match." : {
},
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." : {
},
"Trackers" : {
},
"Trakt.tv" : {
},
"Try different keywords" : {
},
"Use TMDB Poster Image" : {
},
"v%@" : {
},
"View All" : {
},
"Watched" : {
},
"Why am I not seeing any episodes?" : {
},
"You have no items saved." : {
},
"Your downloaded episodes will appear here" : {
}
},
"version" : "1.0"
}

View file

@ -78,7 +78,7 @@ extension JSContext {
}
func setupFetchV2() {
let fetchV2NativeFunction: @convention(block) (String, [String: String]?, String?, String?, ObjCBool, JSValue, JSValue) -> Void = { urlString, headers, method, body, redirect, resolve, reject in
let fetchV2NativeFunction: @convention(block) (String, [String: String]?, String?, String?, ObjCBool, String?, JSValue, JSValue) -> Void = { urlString, headers, method, body, redirect, encoding, resolve, reject in
guard let url = URL(string: urlString) else {
Logger.shared.log("Invalid URL", type: "Error")
DispatchQueue.main.async {
@ -91,7 +91,33 @@ extension JSContext {
var request = URLRequest(url: url)
request.httpMethod = httpMethod
Logger.shared.log("FetchV2 Request: URL=\(url), Method=\(httpMethod), Body=\(body ?? "nil")", type: "Debug")
Logger.shared.log("FetchV2 Request: URL=\(url), Method=\(httpMethod), Body=\(body ?? "nil"), Encoding=\(encoding ?? "utf-8")", type: "Debug")
func getEncoding(from encodingString: String?) -> String.Encoding {
guard let encodingString = encodingString?.lowercased() else {
return .utf8
}
switch encodingString {
case "utf-8", "utf8":
return .utf8
case "windows-1251", "cp1251":
return .windowsCP1251
case "windows-1252", "cp1252":
return .windowsCP1252
case "iso-8859-1", "latin1":
return .isoLatin1
case "ascii":
return .ascii
case "utf-16", "utf16":
return .utf16
default:
Logger.shared.log("Unknown encoding '\(encodingString)', defaulting to UTF-8", type: "Warning")
return .utf8
}
}
let textEncoding = getEncoding(from: encoding)
if httpMethod == "GET", let body = body, !body.isEmpty, body != "null", body != "undefined" {
Logger.shared.log("GET request must not have a body", type: "Error")
@ -164,12 +190,18 @@ extension JSContext {
return
}
if let text = String(data: data, encoding: .utf8) {
if let text = String(data: data, encoding: textEncoding) {
responseDict["body"] = text
callResolve(responseDict)
} else {
Logger.shared.log("Unable to decode data to text", type: "Error")
callResolve(responseDict)
Logger.shared.log("Unable to decode data with encoding \(encoding ?? "utf-8"), trying UTF-8 fallback", type: "Warning")
if let fallbackText = String(data: data, encoding: .utf8) {
responseDict["body"] = fallbackText
callResolve(responseDict)
} else {
Logger.shared.log("Unable to decode data to text with any encoding", type: "Error")
callResolve(responseDict)
}
}
} catch {
@ -184,18 +216,19 @@ extension JSContext {
self.setObject(fetchV2NativeFunction, forKeyedSubscript: "fetchV2Native" as NSString)
let fetchv2Definition = """
function fetchv2(url, headers = {}, method = "GET", body = null, redirect = true ) {
function fetchv2(url, headers = {}, method = "GET", body = null, redirect = true, encoding ) {
var processedBody = null;
if(method != "GET")
{
// Ensure body is properly serialized
processedBody = (body && (typeof body === 'object')) ? JSON.stringify(body) : (body || null)
}
var finalEncoding = encoding || "utf-8";
return new Promise(function(resolve, reject) {
fetchV2Native(url, headers, method, processedBody, redirect, function(rawText) {
fetchV2Native(url, headers, method, processedBody, redirect, finalEncoding, function(rawText) {
const responseObj = {
headers: rawText.headers,
status: rawText.status,

View file

@ -45,7 +45,7 @@ struct ModuleAdditionSettingsView: View {
VStack(spacing: 24) {
if let metadata = moduleMetadata {
VStack(spacing: 0) {
LazyImage(source: URL(string: metadata.iconUrl)) { state in
LazyImage(url: URL(string: metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
@ -72,7 +72,7 @@ struct ModuleAdditionSettingsView: View {
.padding(.top, 6)
HStack(spacing: 10) {
LazyImage(source: URL(string: metadata.author.icon)) { state in
LazyImage(url: URL(string: metadata.author.icon)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()

View file

@ -6,7 +6,7 @@
//
import SwiftUI
/*
struct DeviceScaleModifier: ViewModifier {
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
@ -28,13 +28,14 @@ struct DeviceScaleModifier: ViewModifier {
.position(x: geo.size.width / 2, y: geo.size.height / 2)
}
}
}*/
}
/*
struct DeviceScaleModifier: ViewModifier {
func body(content: Content) -> some View {
content // does nothing for now
}
}
}*/
extension View {

View file

@ -741,7 +741,7 @@ struct EnhancedActiveDownloadCard: View {
HStack(spacing: 16) {
Group {
if let imageURL = download.imageURL {
LazyImage(source: imageURL) { state in
LazyImage(url: imageURL) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
@ -904,7 +904,7 @@ struct EnhancedDownloadGroupCard: View {
HStack(spacing: 16) {
Group {
if let posterURL = group.posterURL {
LazyImage(source: posterURL) { state in
LazyImage(url: posterURL) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
@ -1008,7 +1008,7 @@ struct EnhancedShowEpisodesView: View {
HStack(alignment: .top, spacing: 20) {
Group {
if let posterURL = group.posterURL {
LazyImage(source: posterURL) { state in
LazyImage(url: posterURL) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
@ -1200,7 +1200,7 @@ struct EnhancedEpisodeRow: View {
HStack(spacing: 16) {
Group {
if let backdropURL = asset.metadata?.backdropURL ?? asset.metadata?.posterURL {
LazyImage(source: backdropURL) { state in
LazyImage(url: backdropURL) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()

View file

@ -59,7 +59,7 @@ struct BookmarkCell: View {
var body: some View {
if let module = moduleManager.modules.first(where: { $0.id.uuidString == bookmark.moduleId }) {
ZStack {
LazyImage(source: URL(string: bookmark.imageUrl)) { state in
LazyImage(url: URL(string: bookmark.imageUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
@ -79,7 +79,7 @@ struct BookmarkCell: View {
.fill(Color.black.opacity(0.5))
.frame(width: 28, height: 28)
.overlay(
LazyImage(source: URL(string: module.metadata.iconUrl)) { state in
LazyImage(url: URL(string: module.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()

View file

@ -206,7 +206,7 @@ struct FullWidthContinueWatchingCell: View {
}) {
GeometryReader { geometry in
ZStack(alignment: .bottomLeading) {
LazyImage(source: URL(string: item.imageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : item.imageUrl)) { state in
LazyImage(url: 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()
@ -268,7 +268,7 @@ struct FullWidthContinueWatchingCell: View {
.fill(Color.black.opacity(0.5))
.frame(width: 28, height: 28)
.overlay(
LazyImage(source: URL(string: item.module.metadata.iconUrl)) { state in
LazyImage(url: URL(string: item.module.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()

View file

@ -286,7 +286,7 @@ struct ContinueWatchingCell: View {
}
}) {
ZStack(alignment: .bottomLeading) {
LazyImage(source: URL(string: item.imageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : item.imageUrl)) { state in
LazyImage(url: 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()
@ -357,7 +357,7 @@ struct ContinueWatchingCell: View {
.fill(Color.black.opacity(0.5))
.frame(width: 28, height: 28)
.overlay(
LazyImage(source: URL(string: item.module.metadata.iconUrl)) { state in
LazyImage(url: URL(string: item.module.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
@ -542,7 +542,7 @@ struct BookmarkItemView: View {
isDetailActive = true
}) {
ZStack {
LazyImage(source: URL(string: item.imageUrl)) { state in
LazyImage(url: URL(string: item.imageUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
@ -563,7 +563,7 @@ struct BookmarkItemView: View {
.fill(Color.black.opacity(0.5))
.frame(width: 28, height: 28)
.overlay(
LazyImage(source: URL(string: module.metadata.iconUrl)) { state in
LazyImage(url: URL(string: module.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()

View file

@ -61,7 +61,7 @@ struct AnilistMatchPopupView: View {
HStack(spacing: 12) {
if let cover = result["cover"] as? String,
let url = URL(string: cover) {
LazyImage(source: url) { state in
LazyImage(url: url) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()

View file

@ -47,6 +47,7 @@ struct EpisodeCell: View {
@State private var swipeOffset: CGFloat = 0
@State private var isShowingActions: Bool = false
@State private var actionButtonWidth: CGFloat = 60
@State private var dragState: DragState = .inactive
@State private var retryAttempts: Int = 0
private let maxRetryAttempts: Int = 3
@ -58,6 +59,39 @@ struct EpisodeCell: View {
@Environment(\.colorScheme) private var colorScheme
@AppStorage("selectedAppearance") private var selectedAppearance: Appearance = .system
enum DragState {
case inactive
case pressing
case dragging(translation: CGSize)
var translation: CGSize {
switch self {
case .inactive, .pressing:
return .zero
case .dragging(let translation):
return translation
}
}
var isActive: Bool {
switch self {
case .inactive:
return false
case .pressing, .dragging:
return true
}
}
var isDragging: Bool {
switch self {
case .dragging:
return true
default:
return false
}
}
}
private var downloadStatusString: String {
switch downloadStatus {
case .notDownloaded:
@ -152,67 +186,26 @@ struct EpisodeCell: View {
)
)
.clipShape(RoundedRectangle(cornerRadius: 15))
.offset(x: swipeOffset)
.offset(x: swipeOffset + dragState.translation.width)
.zIndex(1)
.animation(.spring(response: 0.3, dampingFraction: 0.8), value: swipeOffset)
.scaleEffect(dragState.isActive ? 0.98 : 1.0)
.animation(.spring(response: 0.4, dampingFraction: 0.8), value: swipeOffset)
.animation(.spring(response: 0.3, dampingFraction: 0.6), value: dragState.isActive)
.contextMenu {
contextMenuContent
}
.simultaneousGesture(
DragGesture()
DragGesture(coordinateSpace: .local)
.onChanged { value in
let horizontalTranslation = value.translation.width
let verticalTranslation = value.translation.height
let isDefinitelyHorizontalSwipe = abs(horizontalTranslation) > 10 && abs(horizontalTranslation) > abs(verticalTranslation) * 1.5
if isShowingActions || isDefinitelyHorizontalSwipe {
if horizontalTranslation < 0 {
let maxSwipe = calculateMaxSwipeDistance()
swipeOffset = max(horizontalTranslation, -maxSwipe)
} else if isShowingActions {
let maxSwipe = calculateMaxSwipeDistance()
swipeOffset = max(horizontalTranslation - maxSwipe, -maxSwipe)
}
}
handleDragChanged(value)
}
.onEnded { value in
let horizontalTranslation = value.translation.width
let verticalTranslation = value.translation.height
let wasHandlingGesture = abs(horizontalTranslation) > 10 && abs(horizontalTranslation) > abs(verticalTranslation) * 1.5
if isShowingActions || wasHandlingGesture {
let maxSwipe = calculateMaxSwipeDistance()
let threshold = maxSwipe * 0.2
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
if horizontalTranslation < -threshold && !isShowingActions {
swipeOffset = -maxSwipe
isShowingActions = true
} else if horizontalTranslation > threshold && isShowingActions {
swipeOffset = 0
isShowingActions = false
} else {
swipeOffset = isShowingActions ? -maxSwipe : 0
}
}
}
handleDragEnded(value)
}
)
}
.onTapGesture {
if isShowingActions {
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
swipeOffset = 0
isShowingActions = false
}
} else if isMultiSelectMode {
onSelectionChanged?(!isSelected)
} else {
let imageUrl = episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl
onTap(imageUrl)
}
handleTap()
}
.onAppear {
updateProgress()
@ -264,7 +257,7 @@ struct EpisodeCell: View {
private var episodeThumbnail: some View {
ZStack {
if let url = URL(string: episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl) {
LazyImage(source: url) { state in
LazyImage(url: url) { state in
if let image = state.imageContainer?.image {
Image(uiImage: image)
.resizable()
@ -975,13 +968,97 @@ struct EpisodeCell: View {
.padding(.horizontal, 8)
}
private func handleDragChanged(_ value: DragGesture.Value) {
let translation = value.translation
let velocity = value.velocity
let isHorizontalGesture = abs(translation.width) > abs(translation.height)
let hasSignificantHorizontalMovement = abs(translation.width) > 10
if isHorizontalGesture && hasSignificantHorizontalMovement {
dragState = .dragging(translation: .zero)
let proposedOffset = swipeOffset + translation.width
let maxSwipe = calculateMaxSwipeDistance()
if translation.width < 0 {
let newOffset = max(proposedOffset, -maxSwipe)
if proposedOffset < -maxSwipe {
let resistance = abs(proposedOffset + maxSwipe) * 0.15
swipeOffset = -maxSwipe - resistance
} else {
swipeOffset = newOffset
}
} else if isShowingActions {
swipeOffset = max(proposedOffset, -maxSwipe)
}
} else if !hasSignificantHorizontalMovement {
dragState = .inactive
}
}
private func handleDragEnded(_ value: DragGesture.Value) {
let translation = value.translation
let velocity = value.velocity
dragState = .inactive
let isHorizontalGesture = abs(translation.width) > abs(translation.height)
let hasSignificantHorizontalMovement = abs(translation.width) > 10
if isHorizontalGesture && hasSignificantHorizontalMovement {
let maxSwipe = calculateMaxSwipeDistance()
let threshold = maxSwipe * 0.3
let velocityThreshold: CGFloat = 500
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
if translation.width < -threshold || velocity.width < -velocityThreshold {
swipeOffset = -maxSwipe
isShowingActions = true
} else if translation.width > threshold || velocity.width > velocityThreshold {
swipeOffset = 0
isShowingActions = false
} else {
swipeOffset = isShowingActions ? -maxSwipe : 0
}
}
} else {
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
swipeOffset = isShowingActions ? -calculateMaxSwipeDistance() : 0
}
}
}
private func handleTap() {
if isShowingActions {
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
swipeOffset = 0
isShowingActions = false
}
} else if isMultiSelectMode {
onSelectionChanged?(!isSelected)
} else {
let imageUrl = episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl
onTap(imageUrl)
}
}
private func closeActionsIfNeeded() {
if isShowingActions {
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
swipeOffset = 0
isShowingActions = false
}
}
}
private func closeActionsAndPerform(action: @escaping () -> Void) {
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
swipeOffset = 0
isShowingActions = false
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
action()
}
}

View file

@ -123,38 +123,6 @@ struct MediaInfoView: View {
.ignoresSafeArea(.container, edges: .top)
.onAppear {
buttonRefreshTrigger.toggle()
let savedID = UserDefaults.standard.integer(forKey: "custom_anilist_id_\(href)")
if savedID != 0 { customAniListID = savedID }
if let savedPoster = UserDefaults.standard.string(forKey: "tmdbPosterURL_\(href)") {
self.imageUrl = savedPoster
}
if !hasFetched {
DropManager.shared.showDrop(
title: "Fetching Data",
subtitle: "Please wait while fetching.",
duration: 0.5,
icon: UIImage(systemName: "arrow.triangle.2.circlepath")
)
fetchDetails()
if let savedID = UserDefaults.standard.object(forKey: "custom_anilist_id_\(href)") as? Int {
customAniListID = savedID
itemID = savedID
Logger.shared.log("Using custom AniList ID: \(savedID)", type: "Debug")
} else {
fetchMetadataIDIfNeeded()
}
hasFetched = true
AnalyticsManager.shared.sendEvent(
event: "MediaInfoView",
additionalData: ["title": title]
)
}
tabBarController.hideTabBar()
}
.onChange(of: selectedRange) { newValue in
@ -174,12 +142,8 @@ struct MediaInfoView: View {
if let savedPoster = UserDefaults.standard.string(forKey: "tmdbPosterURL_\(href)") {
imageUrl = savedPoster
}
DropManager.shared.showDrop(
title: "Fetching Data",
subtitle: "Please wait while fetching.",
duration: 0.5,
icon: UIImage(systemName: "arrow.triangle.2.circlepath")
)
DropManager.shared.showDrop(title: "Fetching Data", subtitle: "Please wait while fetching.", duration: 0.5, icon: UIImage(systemName: "arrow.triangle.2.circlepath"))
fetchDetails()
if savedCustomID != 0 {
@ -240,7 +204,7 @@ struct MediaInfoView: View {
private var mainScrollView: some View {
ScrollView {
ZStack(alignment: .top) {
LazyImage(source: URL(string: imageUrl)) { state in
LazyImage(url: URL(string: imageUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
@ -255,6 +219,7 @@ struct MediaInfoView: View {
.clipped()
}
}
VStack(spacing: 0) {
Rectangle()
.fill(Color.clear)
@ -283,13 +248,11 @@ struct MediaInfoView: View {
.shadow(color: (colorScheme == .dark ? Color.black : Color.white).opacity(1), radius: 10, x: 0, y: 10)
)
}
.deviceScaled()
}
}
.onAppear {
UIScrollView.appearance().bounces = false
}
.ignoresSafeArea(.container, edges: .top)
}
@ViewBuilder
@ -704,7 +667,7 @@ struct MediaInfoView: View {
@ViewBuilder
private var flatEpisodeList: some View {
LazyVStack(spacing: 15) {
VStack(spacing: 15) {
ForEach(episodeLinks.indices.filter { selectedRange.contains($0) }, id: \.self) { i in
let ep = episodeLinks[i]
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
@ -751,7 +714,7 @@ struct MediaInfoView: View {
private var seasonsEpisodeList: some View {
let seasons = groupedEpisodes()
if !seasons.isEmpty, selectedSeason < seasons.count {
LazyVStack(spacing: 15) {
VStack(spacing: 15) {
ForEach(seasons[selectedSeason]) { ep in
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
@ -1328,12 +1291,16 @@ struct MediaInfoView: View {
videoPlayerViewController.mediaTitle = title
videoPlayerViewController.subtitles = subtitles ?? ""
videoPlayerViewController.aniListID = itemID ?? 0
videoPlayerViewController.modalPresentationStyle = .overFullScreen
videoPlayerViewController.modalPresentationStyle = .fullScreen
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootVC = windowScene.windows.first?.rootViewController {
findTopViewController.findViewController(rootVC).present(videoPlayerViewController, animated: true, completion: nil)
} else {
Logger.shared.log("Failed to find root view controller", type: "Error")
DropManager.shared.showDrop(title: "Error", subtitle: "Failed to present player", duration: 2.0, icon: UIImage(systemName: "xmark.circle"))
}
return
default:
break
@ -1368,7 +1335,7 @@ struct MediaInfoView: View {
episodeImageUrl: selectedEpisodeImage,
headers: headers ?? nil
)
customMediaPlayer.modalPresentationStyle = .overFullScreen
customMediaPlayer.modalPresentationStyle = .fullScreen
Logger.shared.log("Opening custom media player with url: \(url)")
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,

View file

@ -32,7 +32,7 @@ struct SearchResultsGrid: View {
ForEach(items) { item in
NavigationLink(destination: MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: selectedModule)) {
ZStack {
LazyImage(source: URL(string: item.imageUrl)) { state in
LazyImage(url: URL(string: item.imageUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()

View file

@ -71,59 +71,71 @@ struct SearchView: View {
return availableWidth / CGFloat(columnsCount)
}
var body: some View {
NavigationView {
VStack(alignment: .leading) {
HStack {
Text("Search")
.font(.largeTitle)
.fontWeight(.bold)
Spacer()
ModuleSelectorMenu(
selectedModule: selectedModule,
moduleGroups: getModuleLanguageGroups(),
modulesByLanguage: getModulesByLanguage(),
selectedModuleId: selectedModuleId,
onModuleSelected: { moduleId in
selectedModuleId = moduleId
}
)
}
.padding(.horizontal, 20)
.padding(.top, 20)
private var mainContent: some View {
VStack(alignment: .leading) {
HStack {
Text("Search")
.font(.largeTitle)
.fontWeight(.bold)
ScrollView {
SearchContent(
selectedModule: selectedModule,
searchQuery: searchQuery,
searchHistory: searchHistory,
searchItems: searchItems,
isSearching: isSearching,
hasNoResults: hasNoResults,
columns: columns,
columnsCount: columnsCount,
cellWidth: cellWidth,
onHistoryItemSelected: { query in
searchQuery = query
},
onHistoryItemDeleted: { index in
removeFromHistory(at: index)
},
onClearHistory: clearSearchHistory
)
}
.scrollViewBottomPadding()
.simultaneousGesture(
DragGesture().onChanged { _ in
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
Spacer()
ModuleSelectorMenu(
selectedModule: selectedModule,
moduleGroups: getModuleLanguageGroups(),
modulesByLanguage: getModulesByLanguage(),
selectedModuleId: selectedModuleId,
onModuleSelected: { moduleId in
selectedModuleId = moduleId
}
)
}
.navigationBarHidden(true)
.padding(.horizontal, 20)
.padding(.top, 20)
ScrollView {
SearchContent(
selectedModule: selectedModule,
searchQuery: searchQuery,
searchHistory: searchHistory,
searchItems: searchItems,
isSearching: isSearching,
hasNoResults: hasNoResults,
columns: columns,
columnsCount: columnsCount,
cellWidth: cellWidth,
onHistoryItemSelected: { query in
searchQuery = query
},
onHistoryItemDeleted: { index in
removeFromHistory(at: index)
},
onClearHistory: clearSearchHistory
)
}
.scrollViewBottomPadding()
.simultaneousGesture(
DragGesture().onChanged { _ in
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
)
}
.navigationBarHidden(true)
}
var body: some View {
Group {
if #available(iOS 16.0, *) {
NavigationStack {
mainContent
}
} else {
NavigationView {
mainContent
}
.navigationViewStyle(.stack)
}
}
.navigationViewStyle(.stack)
.onAppear {
loadSearchHistory()
if !searchQuery.isEmpty {

View file

@ -27,7 +27,7 @@ struct ModuleSelectorMenu: View {
onModuleSelected(module.id.uuidString)
} label: {
HStack {
LazyImage(source: URL(string: module.metadata.iconUrl)) { state in
LazyImage(url: URL(string: module.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
@ -56,7 +56,7 @@ struct ModuleSelectorMenu: View {
Text(selectedModule.metadata.sourceName)
.font(.headline)
.foregroundColor(.primary)
LazyImage(source: URL(string: selectedModule.metadata.iconUrl)) { state in
LazyImage(url: URL(string: selectedModule.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()

View file

@ -66,7 +66,7 @@ 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) {
LazyImage(source: URL(string: "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/dev/Sora/Assets.xcassets/AppIcons/AppIcon_Default.appiconset/darkmode.png")) { state in
LazyImage(url: 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()
@ -100,7 +100,7 @@ struct SettingsViewAbout: View {
}
}) {
HStack {
LazyImage(source: URL(string: "https://avatars.githubusercontent.com/u/100066266?v=4")) { state in
LazyImage(url: URL(string: "https://avatars.githubusercontent.com/u/100066266?v=4")) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()
@ -213,7 +213,7 @@ struct ContributorView: View {
}
}) {
HStack {
LazyImage(source: URL(string: contributor.avatarUrl)) { state in
LazyImage(url: URL(string: contributor.avatarUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()

View file

@ -67,7 +67,7 @@ fileprivate struct ModuleListItemView: View {
var body: some View {
VStack(spacing: 0) {
HStack {
LazyImage(source: URL(string: module.metadata.iconUrl)) { state in
LazyImage(url: URL(string: module.metadata.iconUrl)) { state in
if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage)
.resizable()

View file

@ -120,7 +120,7 @@ struct SettingsViewTrackers: View {
SettingsSection(title: "AniList") {
VStack(spacing: 0) {
HStack(alignment: .center, spacing: 10) {
LazyImage(source: URL(string: "https://raw.githubusercontent.com/cranci1/Ryu/2f10226aa087154974a70c1ec78aa83a47daced9/Ryu/Assets.xcassets/Listing/Anilist.imageset/anilist.png")) { state in
LazyImage(url: 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()
@ -215,7 +215,7 @@ struct SettingsViewTrackers: View {
SettingsSection(title: "Trakt") {
VStack(spacing: 0) {
HStack(alignment: .center, spacing: 10) {
LazyImage(source: URL(string: "https://static-00.iconduck.com/assets.00/trakt-icon-2048x2048-2633ksxg.png")) { state in
LazyImage(url: 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()

View file

@ -32,6 +32,9 @@
132AF1212D99951700A0140B /* JSController-Streams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132AF1202D99951700A0140B /* JSController-Streams.swift */; };
132AF1232D9995C300A0140B /* JSController-Details.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132AF1222D9995C300A0140B /* JSController-Details.swift */; };
132AF1252D9995F900A0140B /* JSController-Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132AF1242D9995F900A0140B /* JSController-Search.swift */; };
13367ECC2DF70698009CB33F /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = 13367ECB2DF70698009CB33F /* Nuke */; };
13367ECE2DF70698009CB33F /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = 13367ECD2DF70698009CB33F /* NukeUI */; };
13367ED02DF70819009CB33F /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 13367ECF2DF70819009CB33F /* Localizable.xcstrings */; };
133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6D2D2BE2500075467E /* SoraApp.swift */; };
133D7C702D2BE2500075467E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6F2D2BE2500075467E /* ContentView.swift */; };
133D7C722D2BE2520075467E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 133D7C712D2BE2520075467E /* Assets.xcassets */; };
@ -60,7 +63,6 @@
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 */; };
@ -123,6 +125,7 @@
132AF1202D99951700A0140B /* JSController-Streams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSController-Streams.swift"; sourceTree = "<group>"; };
132AF1222D9995C300A0140B /* JSController-Details.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSController-Details.swift"; sourceTree = "<group>"; };
132AF1242D9995F900A0140B /* JSController-Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSController-Search.swift"; sourceTree = "<group>"; };
13367ECF2DF70819009CB33F /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
133D7C6A2D2BE2500075467E /* Sulfur.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sulfur.app; sourceTree = BUILT_PRODUCTS_DIR; };
133D7C6D2D2BE2500075467E /* SoraApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoraApp.swift; sourceTree = "<group>"; };
133D7C6F2D2BE2500075467E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@ -191,9 +194,10 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
13367ECC2DF70698009CB33F /* Nuke in Frameworks */,
13637B902DE0ECD200BDA2FC /* Drops in Frameworks */,
13637B932DE0ECDB00BDA2FC /* MarqueeLabel in Frameworks */,
13BC689F2DF61327009A0651 /* NukeUI in Frameworks */,
13367ECE2DF70698009CB33F /* NukeUI in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -320,6 +324,7 @@
133D7C6F2D2BE2500075467E /* ContentView.swift */,
133D7C712D2BE2520075467E /* Assets.xcassets */,
133D7C732D2BE2520075467E /* Preview Content */,
13367ECF2DF70819009CB33F /* Localizable.xcstrings */,
);
path = Sora;
sourceTree = "<group>";
@ -616,7 +621,8 @@
packageProductDependencies = (
13637B8F2DE0ECD200BDA2FC /* Drops */,
13637B922DE0ECDB00BDA2FC /* MarqueeLabel */,
13BC689E2DF61327009A0651 /* NukeUI */,
13367ECB2DF70698009CB33F /* Nuke */,
13367ECD2DF70698009CB33F /* NukeUI */,
);
productName = Sora;
productReference = 133D7C6A2D2BE2500075467E /* Sulfur.app */;
@ -648,7 +654,7 @@
packageReferences = (
13637B8E2DE0ECD200BDA2FC /* XCRemoteSwiftPackageReference "Drops" */,
13637B912DE0ECDB00BDA2FC /* XCRemoteSwiftPackageReference "MarqueeLabel" */,
13BC689D2DF61327009A0651 /* XCRemoteSwiftPackageReference "NukeUI" */,
13367ECA2DF70698009CB33F /* XCRemoteSwiftPackageReference "Nuke" */,
);
productRefGroup = 133D7C6B2D2BE2500075467E /* Products */;
projectDirPath = "";
@ -666,6 +672,7 @@
files = (
133D7C752D2BE2520075467E /* Preview Assets.xcassets in Resources */,
133D7C722D2BE2520075467E /* Assets.xcassets in Resources */,
13367ED02DF70819009CB33F /* Localizable.xcstrings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -820,11 +827,18 @@
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = "1,2,6";
VALID_ARCHS = "arm64 x86_64";
"VALID_ARCHS[sdk=iphoneos*]" = "arm64 arm64e";
"VALID_ARCHS[sdk=iphonesimulator*]" = "arm64 x86_64";
"VALID_ARCHS[sdk=macosx*]" = "arm64 x86_64";
};
name = Debug;
};
@ -877,12 +891,19 @@
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2,6";
VALIDATE_PRODUCT = YES;
VALID_ARCHS = "arm64 x86_64";
"VALID_ARCHS[sdk=iphoneos*]" = "arm64 arm64e";
"VALID_ARCHS[sdk=iphonesimulator*]" = "arm64 x86_64";
"VALID_ARCHS[sdk=macosx*]" = "arm64 x86_64";
};
name = Release;
};
@ -907,24 +928,24 @@
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
MARKETING_VERSION = 0.3.0;
PRODUCT_BUNDLE_IDENTIFIER = me.cranci.sulfur;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@ -949,24 +970,24 @@
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
MARKETING_VERSION = 0.3.0;
PRODUCT_BUNDLE_IDENTIFIER = me.cranci.sulfur;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@ -994,6 +1015,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
13367ECA2DF70698009CB33F /* XCRemoteSwiftPackageReference "Nuke" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kean/Nuke.git";
requirement = {
branch = main;
kind = branch;
};
};
13637B8E2DE0ECD200BDA2FC /* XCRemoteSwiftPackageReference "Drops" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/omaralbeik/Drops.git";
@ -1006,21 +1035,23 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/cbpowell/MarqueeLabel";
requirement = {
kind = exactVersion;
version = 4.2.1;
};
};
13BC689D2DF61327009A0651 /* XCRemoteSwiftPackageReference "NukeUI" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kean/NukeUI";
requirement = {
branch = main;
branch = master;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
13367ECB2DF70698009CB33F /* Nuke */ = {
isa = XCSwiftPackageProductDependency;
package = 13367ECA2DF70698009CB33F /* XCRemoteSwiftPackageReference "Nuke" */;
productName = Nuke;
};
13367ECD2DF70698009CB33F /* NukeUI */ = {
isa = XCSwiftPackageProductDependency;
package = 13367ECA2DF70698009CB33F /* XCRemoteSwiftPackageReference "Nuke" */;
productName = NukeUI;
};
13637B8F2DE0ECD200BDA2FC /* Drops */ = {
isa = XCSwiftPackageProductDependency;
package = 13637B8E2DE0ECD200BDA2FC /* XCRemoteSwiftPackageReference "Drops" */;
@ -1031,11 +1062,6 @@
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

@ -1,52 +1,32 @@
{
"object": {
"pins": [
{
"package": "Drops",
"repositoryURL": "https://github.com/omaralbeik/Drops.git",
"state": {
"branch": "main",
"revision": "5824681795286c36bdc4a493081a63e64e2a064e",
"version": null
}
},
{
"package": "Gifu",
"repositoryURL": "https://github.com/kaishin/Gifu",
"state": {
"branch": null,
"revision": "82da0086dea14ca9afc9801234ad8dc4cd9e2738",
"version": "3.4.1"
}
},
{
"package": "MarqueeLabel",
"repositoryURL": "https://github.com/cbpowell/MarqueeLabel",
"state": {
"branch": null,
"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
}
"pins" : [
{
"identity" : "drops",
"kind" : "remoteSourceControl",
"location" : "https://github.com/omaralbeik/Drops.git",
"state" : {
"branch" : "main",
"revision" : "5824681795286c36bdc4a493081a63e64e2a064e"
}
]
},
"version": 1
},
{
"identity" : "marqueelabel",
"kind" : "remoteSourceControl",
"location" : "https://github.com/cbpowell/MarqueeLabel",
"state" : {
"branch" : "master",
"revision" : "18e4787f4dc1c26d2d581c4bc9aeae34686eeeae"
}
},
{
"identity" : "nuke",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Nuke.git",
"state" : {
"branch" : "main",
"revision" : "c7ba4833b1b38f09e9708858aeaf91babc69f65c"
}
}
],
"version" : 2
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 73 KiB

View file

@ -16,19 +16,35 @@ cd build
xcodebuild -project "$WORKING_LOCATION/$APPLICATION_NAME.xcodeproj" \
-scheme "$APPLICATION_NAME" \
-configuration Release \
-derivedDataPath "$WORKING_LOCATION/build/DerivedDataApp" \
-destination 'platform=macOS,variant=Mac Catalyst' \
-derivedDataPath "$WORKING_LOCATION/build/DerivedDataApp-x86_64" \
-destination 'platform=macOS,arch=x86_64,variant=Mac Catalyst' \
clean build \
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO"
DD_APP_PATH="$WORKING_LOCATION/build/DerivedDataApp/Build/Products/Release-maccatalyst/$APPLICATION_NAME.app"
xcodebuild -project "$WORKING_LOCATION/$APPLICATION_NAME.xcodeproj" \
-scheme "$APPLICATION_NAME" \
-configuration Release \
-derivedDataPath "$WORKING_LOCATION/build/DerivedDataApp-arm64" \
-destination 'platform=macOS,arch=arm64,variant=Mac Catalyst' \
clean build \
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO"
DD_APP_PATH_X86_64="$WORKING_LOCATION/build/DerivedDataApp-x86_64/Build/Products/Release-maccatalyst/$APPLICATION_NAME.app"
DD_APP_PATH_ARM64="$WORKING_LOCATION/build/DerivedDataApp-arm64/Build/Products/Release-maccatalyst/$APPLICATION_NAME.app"
TARGET_APP="$WORKING_LOCATION/build/$APPLICATION_NAME.app"
cp -r "$DD_APP_PATH" "$TARGET_APP"
rm -rf "$TARGET_APP"
cp -r "$DD_APP_PATH_ARM64" "$TARGET_APP"
lipo -create \
"$DD_APP_PATH_X86_64/Contents/MacOS/$APPLICATION_NAME" \
"$DD_APP_PATH_ARM64/Contents/MacOS/$APPLICATION_NAME" \
-output "$TARGET_APP/Contents/MacOS/$APPLICATION_NAME"
codesign --remove "$TARGET_APP"
if [ -e "$TARGET_APP/_CodeSignature" ]; then
rm -rf "$TARGET_APP/_CodeSignature"
fi
echo "Mac Catalyst build completed: $TARGET_APP"
echo "Universal Mac Catalyst build completed: $TARGET_APP"
lipo -archs "$TARGET_APP/Contents/MacOS/$APPLICATION_NAME"