mirror of
https://github.com/yattee/yattee.git
synced 2026-04-09 17:16:57 +00:00
Fix 5 TestFlight crash types from builds 250-254
- Fix BGTaskScheduler assertion crash on Mac Catalyst by guarding all iOS background task APIs with isMacCatalystApp check - Fix iPad popover crash in UIPopoverPresentationController by adding .presentationCompactAdaptation(.sheet) to all 27 confirmationDialogs - Fix SwiftData assertion crash when accessing deleted Bookmark model properties during SwiftUI hit testing in BookmarkRowView - Fix UICollectionView invalid item count crash on queue swipe-to-delete by using ID-based removal with withAnimation instead of stale index - Fix Range crash in storyboard download when storyboardCount is zero
This commit is contained in:
@@ -43,6 +43,7 @@ final class BackgroundRefreshManager {
|
|||||||
|
|
||||||
func registerBackgroundTasks() {
|
func registerBackgroundTasks() {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
guard !ProcessInfo.processInfo.isMacCatalystApp else { return }
|
||||||
registerIOSBackgroundTask()
|
registerIOSBackgroundTask()
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
registerMacOSBackgroundActivity()
|
registerMacOSBackgroundActivity()
|
||||||
@@ -64,6 +65,7 @@ final class BackgroundRefreshManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scheduleIOSBackgroundRefresh() {
|
func scheduleIOSBackgroundRefresh() {
|
||||||
|
guard !ProcessInfo.processInfo.isMacCatalystApp else { return }
|
||||||
let request = BGAppRefreshTaskRequest(identifier: Self.backgroundTaskIdentifier)
|
let request = BGAppRefreshTaskRequest(identifier: Self.backgroundTaskIdentifier)
|
||||||
// Request to run in ~15 minutes (system decides actual timing)
|
// Request to run in ~15 minutes (system decides actual timing)
|
||||||
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
|
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
|
||||||
@@ -77,6 +79,7 @@ final class BackgroundRefreshManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cancelIOSBackgroundRefresh() {
|
func cancelIOSBackgroundRefresh() {
|
||||||
|
guard !ProcessInfo.processInfo.isMacCatalystApp else { return }
|
||||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: Self.backgroundTaskIdentifier)
|
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: Self.backgroundTaskIdentifier)
|
||||||
LoggingService.shared.debug("Cancelled iOS background refresh", category: .notifications)
|
LoggingService.shared.debug("Cancelled iOS background refresh", category: .notifications)
|
||||||
}
|
}
|
||||||
@@ -156,6 +159,7 @@ final class BackgroundRefreshManager {
|
|||||||
|
|
||||||
func handleNotificationsEnabledChanged(_ enabled: Bool) {
|
func handleNotificationsEnabledChanged(_ enabled: Bool) {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
guard !ProcessInfo.processInfo.isMacCatalystApp else { return }
|
||||||
if enabled {
|
if enabled {
|
||||||
scheduleIOSBackgroundRefresh()
|
scheduleIOSBackgroundRefresh()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ extension DownloadManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If VTT parsing failed, fall back to direct URLs (may not work if blocked)
|
// If VTT parsing failed, fall back to direct URLs (may not work if blocked)
|
||||||
if imageURLs.isEmpty {
|
if imageURLs.isEmpty, storyboard.storyboardCount > 0 {
|
||||||
for sheetIndex in 0..<storyboard.storyboardCount {
|
for sheetIndex in 0..<storyboard.storyboardCount {
|
||||||
if let url = storyboard.directSheetURL(for: sheetIndex) {
|
if let url = storyboard.directSheetURL(for: sheetIndex) {
|
||||||
imageURLs.append(url)
|
imageURLs.append(url)
|
||||||
|
|||||||
@@ -539,6 +539,11 @@ final class PlayerState {
|
|||||||
queue.remove(at: index)
|
queue.remove(at: index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes a video from the queue by its ID.
|
||||||
|
func removeFromQueue(id: QueuedVideo.ID) {
|
||||||
|
queue.removeAll { $0.id == id }
|
||||||
|
}
|
||||||
|
|
||||||
/// Updates a queue item with preloaded video details and streams.
|
/// Updates a queue item with preloaded video details and streams.
|
||||||
/// Preserves the item's ID for stable SwiftUI identity.
|
/// Preserves the item's ID for stable SwiftUI identity.
|
||||||
func updateQueueItemWithPreload(at index: Int, video: Video, stream: Stream?, audioStream: Stream?) {
|
func updateQueueItemWithPreload(at index: Int, video: Video, stream: Stream?, audioStream: Stream?) {
|
||||||
|
|||||||
@@ -122,6 +122,11 @@ final class QueueManager {
|
|||||||
playerState?.removeFromQueue(at: index)
|
playerState?.removeFromQueue(at: index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes a video from the queue by its ID.
|
||||||
|
func removeFromQueue(id: QueuedVideo.ID) {
|
||||||
|
playerState?.removeFromQueue(id: id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Moves a queue item from one position to another.
|
/// Moves a queue item from one position to another.
|
||||||
func moveQueueItem(from sourceIndex: Int, to destinationIndex: Int) {
|
func moveQueueItem(from sourceIndex: Int, to destinationIndex: Int) {
|
||||||
playerState?.moveQueueItem(from: sourceIndex, to: destinationIndex)
|
playerState?.moveQueueItem(from: sourceIndex, to: destinationIndex)
|
||||||
|
|||||||
@@ -368,6 +368,7 @@ struct ChannelView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "channel.unsubscribe.confirmation.message"))
|
Text(String(localized: "channel.unsubscribe.confirmation.message"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Loading Content
|
// MARK: - Loading Content
|
||||||
@@ -475,6 +476,7 @@ struct ChannelView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "channel.unsubscribe.confirmation.message"))
|
Text(String(localized: "channel.unsubscribe.confirmation.message"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Header
|
// MARK: - Header
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
// Row view for displaying bookmarked videos with tags and notes.
|
// Row view for displaying bookmarked videos with tags and notes.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import SwiftData
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
/// Row view for displaying bookmarked videos.
|
/// Row view for displaying bookmarked videos.
|
||||||
@@ -30,15 +31,21 @@ struct BookmarkRowView: View {
|
|||||||
appEnvironment?.settingsManager.accentColor.color ?? .accentColor
|
appEnvironment?.settingsManager.accentColor.color ?? .accentColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isBookmarkValid: Bool {
|
||||||
|
bookmark.modelContext != nil && !bookmark.isDeleted
|
||||||
|
}
|
||||||
|
|
||||||
private var video: Video {
|
private var video: Video {
|
||||||
bookmark.toVideo()
|
bookmark.toVideo()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var hasTags: Bool {
|
private var hasTags: Bool {
|
||||||
!bookmark.tags.isEmpty
|
guard isBookmarkValid else { return false }
|
||||||
|
return !bookmark.tags.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
private var hasNote: Bool {
|
private var hasNote: Bool {
|
||||||
|
guard isBookmarkValid else { return false }
|
||||||
if let note = bookmark.note, !note.isEmpty {
|
if let note = bookmark.note, !note.isEmpty {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -56,6 +63,12 @@ struct BookmarkRowView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
if isBookmarkValid {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var content: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
// Video row content
|
// Video row content
|
||||||
videoRowContent
|
videoRowContent
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ struct DownloadsStorageView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text("settings.downloads.storage.deleteWatched.message \(watchedDownloads.count) \(formatBytes(watchedDownloadsSize))")
|
Text("settings.downloads.storage.deleteWatched.message \(watchedDownloads.count) \(formatBytes(watchedDownloadsSize))")
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
.confirmationDialog(
|
.confirmationDialog(
|
||||||
String(localized: "settings.downloads.storage.deleteAll"),
|
String(localized: "settings.downloads.storage.deleteAll"),
|
||||||
isPresented: $showingDeleteAllConfirmation,
|
isPresented: $showingDeleteAllConfirmation,
|
||||||
@@ -101,6 +102,7 @@ struct DownloadsStorageView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text("settings.downloads.storage.deleteAll.message \(completedDownloads.count) \(formatBytes(allDownloadsSize))")
|
Text("settings.downloads.storage.deleteAll.message \(completedDownloads.count) \(formatBytes(allDownloadsSize))")
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Delete Menu
|
// MARK: - Delete Menu
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ struct HistoryListView: View {
|
|||||||
selectedClearOption = nil
|
selectedClearOption = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
loadHistory()
|
loadHistory()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ struct MediaSourcesView: View {
|
|||||||
pendingDeleteSource = nil
|
pendingDeleteSource = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|||||||
@@ -135,7 +135,9 @@ struct QueueManagementSheet: View {
|
|||||||
index: index + 1, // +1 because "Now Playing" is shown as position 1
|
index: index + 1, // +1 because "Now Playing" is shown as position 1
|
||||||
isCurrentlyPlaying: false,
|
isCurrentlyPlaying: false,
|
||||||
onRemove: {
|
onRemove: {
|
||||||
queueManager?.removeFromQueue(at: index)
|
withAnimation {
|
||||||
|
queueManager?.removeFromQueue(id: queuedVideo.id)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onTap: {
|
onTap: {
|
||||||
playVideo(at: index)
|
playVideo(at: index)
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ struct UnifiedPlaylistDetailView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
.task {
|
.task {
|
||||||
await loadPlaylist()
|
await loadPlaylist()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -615,6 +615,7 @@ struct SearchView: View {
|
|||||||
}
|
}
|
||||||
Button(String(localized: "common.cancel"), role: .cancel) {}
|
Button(String(localized: "common.cancel"), role: .cancel) {}
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ struct AddRemoteServerView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "sources.yatteeServer.warning.message"))
|
Text(String(localized: "sources.yatteeServer.warning.message"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
#else
|
#else
|
||||||
formContent
|
formContent
|
||||||
.navigationTitle(String(localized: "sources.addRemoteServer"))
|
.navigationTitle(String(localized: "sources.addRemoteServer"))
|
||||||
@@ -118,6 +119,7 @@ struct AddRemoteServerView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "sources.yatteeServer.warning.message"))
|
Text(String(localized: "sources.yatteeServer.warning.message"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ struct AdvancedSettingsView: View {
|
|||||||
}
|
}
|
||||||
Button(String(localized: "common.cancel"), role: .cancel) {}
|
Button(String(localized: "common.cancel"), role: .cancel) {}
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
userAgentText = settingsManager.customUserAgent
|
userAgentText = settingsManager.customUserAgent
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
@@ -68,6 +69,7 @@ struct AdvancedSettingsView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "settings.advanced.storage.deleteOrphaned.message \(orphanedFilesCount) \(formatBytes(orphanedFilesSize))"))
|
Text(String(localized: "settings.advanced.storage.deleteOrphaned.message \(orphanedFilesCount) \(formatBytes(orphanedFilesSize))"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
.alert(
|
.alert(
|
||||||
String(localized: "settings.advanced.storage.cleanupComplete"),
|
String(localized: "settings.advanced.storage.cleanupComplete"),
|
||||||
isPresented: $showingOrphanCleanupResult
|
isPresented: $showingOrphanCleanupResult
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ struct DeveloperSettingsView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "settings.advanced.data.removeDuplicates.message"))
|
Text(String(localized: "settings.advanced.data.removeDuplicates.message"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
.alert(
|
.alert(
|
||||||
String(localized: "settings.advanced.data.deduplicationComplete"),
|
String(localized: "settings.advanced.data.deduplicationComplete"),
|
||||||
isPresented: $showingDeduplicationResult
|
isPresented: $showingDeduplicationResult
|
||||||
@@ -71,6 +72,7 @@ struct DeveloperSettingsView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "settings.advanced.data.resetICloud.message"))
|
Text(String(localized: "settings.advanced.data.resetICloud.message"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
.alert(
|
.alert(
|
||||||
String(localized: "settings.advanced.data.iCloudReset"),
|
String(localized: "settings.advanced.data.iCloudReset"),
|
||||||
isPresented: $showingResetiCloudComplete
|
isPresented: $showingResetiCloudComplete
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ private struct EditRemoteServerContent: View {
|
|||||||
}
|
}
|
||||||
Button(String(localized: "common.cancel"), role: .cancel) {}
|
Button(String(localized: "common.cancel"), role: .cancel) {}
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
.sheet(isPresented: $showLoginSheet) {
|
.sheet(isPresented: $showLoginSheet) {
|
||||||
InstanceLoginView(instance: instance) { credential in
|
InstanceLoginView(instance: instance) { credential in
|
||||||
appEnvironment?.credentialsManager(for: instance)?.setCredential(credential, for: instance)
|
appEnvironment?.credentialsManager(for: instance)?.setCredential(credential, for: instance)
|
||||||
@@ -366,6 +367,7 @@ private struct EditRemoteServerContent: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "sources.yatteeServer.warning.message"))
|
Text(String(localized: "sources.yatteeServer.warning.message"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Computed Properties
|
// MARK: - Computed Properties
|
||||||
@@ -733,6 +735,7 @@ private struct EditFileSourceContent: View {
|
|||||||
}
|
}
|
||||||
Button(String(localized: "common.cancel"), role: .cancel) {}
|
Button(String(localized: "common.cancel"), role: .cancel) {}
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ struct ImportPlaylistsView: View {
|
|||||||
Task { await addAllPlaylists() }
|
Task { await addAllPlaylists() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
.confirmationDialog(
|
.confirmationDialog(
|
||||||
String(localized: "import.playlists.mergeWarning.title"),
|
String(localized: "import.playlists.mergeWarning.title"),
|
||||||
isPresented: $showMergeWarning,
|
isPresented: $showMergeWarning,
|
||||||
@@ -97,6 +98,7 @@ struct ImportPlaylistsView: View {
|
|||||||
Text(String(localized: "import.playlists.mergeWarning.message \(playlist.title)"))
|
Text(String(localized: "import.playlists.mergeWarning.message \(playlist.title)"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
.task {
|
.task {
|
||||||
await loadPlaylists()
|
await loadPlaylists()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ struct ImportSubscriptionsView: View {
|
|||||||
addAllSubscriptions()
|
addAllSubscriptions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
.task {
|
.task {
|
||||||
await loadSubscriptions()
|
await loadSubscriptions()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,6 +318,7 @@ private struct EditMPVOptionSheet: View {
|
|||||||
}
|
}
|
||||||
Button(String(localized: "common.cancel"), role: .cancel) {}
|
Button(String(localized: "common.cancel"), role: .cancel) {}
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
}
|
}
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.frame(minWidth: 400, minHeight: 200)
|
.frame(minWidth: 400, minHeight: 200)
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ struct SourcesListView: View {
|
|||||||
pendingDeleteSource = nil
|
pendingDeleteSource = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Empty State
|
// MARK: - Empty State
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ struct SubscriptionsSettingsView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "settings.subscriptions.account.switch.message"))
|
Text(String(localized: "settings.subscriptions.account.switch.message"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Account Section
|
// MARK: - Account Section
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ struct iCloudSettingsView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "settings.icloud.enable.confirmation.message"))
|
Text(String(localized: "settings.icloud.enable.confirmation.message"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
.confirmationDialog(
|
.confirmationDialog(
|
||||||
String(localized: "settings.icloud.disable.confirmation.title"),
|
String(localized: "settings.icloud.disable.confirmation.title"),
|
||||||
isPresented: $showingDisableConfirmation,
|
isPresented: $showingDisableConfirmation,
|
||||||
@@ -113,6 +114,7 @@ struct iCloudSettingsView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "settings.icloud.disable.confirmation.message"))
|
Text(String(localized: "settings.icloud.disable.confirmation.message"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
// Confirmation for categories that replace local data (instances, settings, media sources)
|
// Confirmation for categories that replace local data (instances, settings, media sources)
|
||||||
.confirmationDialog(
|
.confirmationDialog(
|
||||||
String(localized: "settings.icloud.category.enable.title"),
|
String(localized: "settings.icloud.category.enable.title"),
|
||||||
@@ -134,6 +136,7 @@ struct iCloudSettingsView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "settings.icloud.category.enable.message"))
|
Text(String(localized: "settings.icloud.category.enable.message"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
// Confirmation for categories that upload/merge local data (subscriptions, bookmarks, playlists, history)
|
// Confirmation for categories that upload/merge local data (subscriptions, bookmarks, playlists, history)
|
||||||
.confirmationDialog(
|
.confirmationDialog(
|
||||||
String(localized: "settings.icloud.category.sync.title"),
|
String(localized: "settings.icloud.category.sync.title"),
|
||||||
@@ -155,6 +158,7 @@ struct iCloudSettingsView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(String(localized: "settings.icloud.category.sync.message"))
|
Text(String(localized: "settings.icloud.category.sync.message"))
|
||||||
}
|
}
|
||||||
|
.presentationCompactAdaptation(.sheet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Sync Categories Section
|
// MARK: - Sync Categories Section
|
||||||
|
|||||||
Reference in New Issue
Block a user