Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Nextcloud.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
AABD0C8A2D5F67A400F009E6 /* XCUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABD0C892D5F67A200F009E6 /* XCUIElement.swift */; };
AABD0C9B2D5F73FC00F009E6 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABD0C9A2D5F73FA00F009E6 /* Placeholder.swift */; };
AAE330042D2ED20200B04903 /* NCShareNavigationTitleSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE330032D2ED1FF00B04903 /* NCShareNavigationTitleSetting.swift */; };
AAFC0D042F9AA10000F0A001 /* NCFocusedAutoUploadIntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFC0D012F9AA10000F0A001 /* NCFocusedAutoUploadIntroView.swift */; };
AAFC0D052F9AA10000F0A001 /* NCFocusedAutoUploadProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFC0D022F9AA10000F0A001 /* NCFocusedAutoUploadProgressView.swift */; };
AAFC0D062F9AA10000F0A001 /* NCFocusedAutoUploadScreenDimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFC0D032F9AA10000F0A001 /* NCFocusedAutoUploadScreenDimmer.swift */; };
AAFC0D092F9AA10000F0A001 /* NCFocusedAutoUploadCloudAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFC0D082F9AA10000F0A001 /* NCFocusedAutoUploadCloudAnimation.swift */; };
AB6000012F60000100FE2775 /* NCTagEditorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB6000002F60000100FE2775 /* NCTagEditorModel.swift */; };
AB6000032F60000200FE2775 /* NCTagEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB6000022F60000200FE2775 /* NCTagEditorView.swift */; };
AF1A9B6427D0CA1E00F17A9E /* UIAlertController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1A9B6327D0CA1E00F17A9E /* UIAlertController+Extension.swift */; };
Expand Down Expand Up @@ -1207,6 +1211,10 @@
AACCAB632CFE04F700DA1786 /* lo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lo; path = lo.lproj/Localizable.strings; sourceTree = "<group>"; };
AACCAB642CFE04F700DA1786 /* lo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lo; path = lo.lproj/InfoPlist.strings; sourceTree = "<group>"; };
AAE330032D2ED1FF00B04903 /* NCShareNavigationTitleSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareNavigationTitleSetting.swift; sourceTree = "<group>"; };
AAFC0D012F9AA10000F0A001 /* NCFocusedAutoUploadIntroView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCFocusedAutoUploadIntroView.swift; sourceTree = "<group>"; };
AAFC0D022F9AA10000F0A001 /* NCFocusedAutoUploadProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCFocusedAutoUploadProgressView.swift; sourceTree = "<group>"; };
AAFC0D032F9AA10000F0A001 /* NCFocusedAutoUploadScreenDimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCFocusedAutoUploadScreenDimmer.swift; sourceTree = "<group>"; };
AAFC0D082F9AA10000F0A001 /* NCFocusedAutoUploadCloudAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCFocusedAutoUploadCloudAnimation.swift; sourceTree = "<group>"; };
AB6000002F60000100FE2775 /* NCTagEditorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCTagEditorModel.swift; sourceTree = "<group>"; };
AB6000022F60000200FE2775 /* NCTagEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCTagEditorView.swift; sourceTree = "<group>"; };
AF1A9B6327D0CA1E00F17A9E /* UIAlertController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Extension.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2619,6 +2627,10 @@
F39A1EE12D0AF8A200DAD522 /* Albums.swift */,
F768821B2C0DD1E7001CF441 /* NCAutoUploadView.swift */,
F71D2FB62E09BBD700B751CC /* NCAutoUploadModel.swift */,
AAFC0D012F9AA10000F0A001 /* NCFocusedAutoUploadIntroView.swift */,
AAFC0D022F9AA10000F0A001 /* NCFocusedAutoUploadProgressView.swift */,
AAFC0D032F9AA10000F0A001 /* NCFocusedAutoUploadScreenDimmer.swift */,
AAFC0D082F9AA10000F0A001 /* NCFocusedAutoUploadCloudAnimation.swift */,
);
path = AutoUpload;
sourceTree = "<group>";
Expand Down Expand Up @@ -4689,6 +4701,10 @@
F76D364628A4F8BF00214537 /* NCActivityIndicator.swift in Sources */,
F3A047992BD2668800658E7B /* NCAssistantModel.swift in Sources */,
F76882322C0DD1E7001CF441 /* NCAutoUploadView.swift in Sources */,
AAFC0D042F9AA10000F0A001 /* NCFocusedAutoUploadIntroView.swift in Sources */,
AAFC0D052F9AA10000F0A001 /* NCFocusedAutoUploadProgressView.swift in Sources */,
AAFC0D062F9AA10000F0A001 /* NCFocusedAutoUploadScreenDimmer.swift in Sources */,
AAFC0D092F9AA10000F0A001 /* NCFocusedAutoUploadCloudAnimation.swift in Sources */,
F36E64F72B9245210085ABB5 /* NCCollectionViewCommon+SelectTabBarDelegate.swift in Sources */,
F79A65C62191D95E00FF6DCC /* NCSelect.swift in Sources */,
F75D19E325EFE09000D74598 /* NCContextMenuTrash.swift in Sources */,
Expand Down
33 changes: 33 additions & 0 deletions iOSClient/Data/NCManageDatabase+AutoUpload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,39 @@ extension NCManageDatabase {
}
}

func countAutoUploadMetadatasAsync(account: String,
autoUploadServerUrlBase: String,
transfersSuccess: [tableMetadata] = []) async -> Int {
let global = NCGlobal.shared
let excludedIds = Set(transfersSuccess.compactMap { metadata -> String? in
guard metadata.account == account,
metadata.sessionSelector == global.selectorUploadAutoUpload,
metadata.autoUploadServerUrlBase == autoUploadServerUrlBase,
!metadata.ocIdTransfer.isEmpty else {
return nil
}

return metadata.ocIdTransfer
})

return await core.performRealmReadAsync { realm in
let results = realm.objects(tableMetadata.self)
.filter("account == %@ AND autoUploadServerUrlBase == %@ AND directory == false AND sessionSelector == %@ AND status IN %@",
account,
autoUploadServerUrlBase,
global.selectorUploadAutoUpload,
global.metadataStatusUploadingAllMode)

guard !excludedIds.isEmpty else {
return results.count
}

return results
.filter("NOT (ocIdTransfer IN %@)", Array(excludedIds))
.count
} ?? 0
Comment on lines +136 to +139
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We filter out auto upload success files. Is that ok? @marinofaggiana

}

func existsAutoUpload(account: String,
autoUploadServerUrlBase: String) -> Bool {
return core.performRealmRead { realm in
Expand Down
58 changes: 58 additions & 0 deletions iOSClient/Settings/AutoUpload/NCAutoUploadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ struct NCAutoUploadView: View {
@State private var showUploadFolder = false
@State private var showSelectAlbums = false
@State private var showUploadAllPhotosWarning = false
@State private var showFocusedAutoUploadIntro = false
@State private var showFocusedAutoUploadProgress = false
@State private var openFocusedAutoUploadAfterIntro = false
@State private var startAutoUpload = false

var body: some View {
Expand Down Expand Up @@ -49,6 +52,31 @@ struct NCAutoUploadView: View {
ConfirmAutoUploadSheet(model: model, isPresented: $showUploadAllPhotosWarning)
.presentationDetents([.medium, .large])
}
.sheet(isPresented: $showFocusedAutoUploadIntro, onDismiss: {
guard openFocusedAutoUploadAfterIntro else { return }

openFocusedAutoUploadAfterIntro = false
showFocusedAutoUploadProgress = true
}) {
NCFocusedAutoUploadIntroView {
openFocusedAutoUploadAfterIntro = true
showFocusedAutoUploadIntro = false
}
.presentationDetents([.large])
}
.fullScreenCover(isPresented: $showFocusedAutoUploadProgress) {
NCFocusedAutoUploadProgressView(isPresented: $showFocusedAutoUploadProgress,
account: model.session.account,
urlBase: model.session.urlBase,
userId: model.session.userId)
}
.onChange(of: model.autoUploadStart) { _, newValue in
if !newValue {
showFocusedAutoUploadIntro = false
showFocusedAutoUploadProgress = false
openFocusedAutoUploadAfterIntro = false
}
}
}

@ViewBuilder
Expand Down Expand Up @@ -207,6 +235,36 @@ struct NCAutoUploadView: View {
})
}
.disabled(model.autoUploadStart)

if model.autoUploadStart {
Section(content: {
Button {
showFocusedAutoUploadIntro = true
} label: {
HStack {
Image(systemName: "moon")
.font(.icon())
.frame(width: 26)
.foregroundColor(Color(NCBrandColor.shared.iconImageColor))

Text(NSLocalizedString("_focused_auto_upload_", comment: ""))
.font(.body)
.foregroundStyle(.primary)

Spacer()

Image(systemName: "chevron.right")
.font(.footnote)
.fontWeight(.semibold)
.foregroundStyle(.tertiary)
}
}
.buttonStyle(.plain)
}, footer: {
Text(NSLocalizedString("_focused_auto_upload_settings_footer_", comment: ""))
.font(.footnote)
})
}
}
.safeAreaInset(edge: .bottom) {
autoUploadStartButton
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: Nextcloud GmbH
// SPDX-FileCopyrightText: 2026 Milen Pivchev
// SPDX-License-Identifier: GPL-3.0-or-later

import SwiftUI

struct NCFocusedAutoUploadCloudAnimation: View {
let size: CGFloat
let cloudColor: Color
let arrowColor: Color
let isAnimated: Bool

init(size: CGFloat = 176,
cloudColor: Color = .white,
arrowColor: Color = .black.opacity(0.82),
isAnimated: Bool = true) {
self.size = size
self.cloudColor = cloudColor
self.arrowColor = arrowColor
self.isAnimated = isAnimated
}

var body: some View {
if isAnimated {
animatedCloud
} else {
staticCloud
}
}

private var animatedCloud: some View {
TimelineView(.animation) { timeline in
cloud(progress: animationProgress(at: timeline.date), includesMotion: true)
}
.frame(width: size, height: size * 0.82)
.accessibilityHidden(true)
}

private var staticCloud: some View {
cloud(progress: 0, includesMotion: false)
.frame(width: size, height: size * 0.82)
.accessibilityHidden(true)
}

private func cloud(progress: Double, includesMotion: Bool) -> some View {
ZStack {
Image(systemName: includesMotion ? "icloud.fill" : "icloud")
.font(.system(size: size * 0.53, weight: .regular))
.foregroundStyle(cloudColor)
.scaleEffect(includesMotion ? interpolate(from: 0.96, to: 1.03, progress: progress) : 1)
.offset(y: includesMotion ? interpolate(from: size * 0.03, to: -size * 0.03, progress: progress) : 0)

Image(systemName: "arrow.up")
.font(.system(size: includesMotion ? size * 0.17 : size * 0.22, weight: .bold))
.foregroundStyle(arrowColor)
.offset(y: includesMotion ? interpolate(from: -size * 0.02, to: -size * 0.09, progress: progress) : 0)
}
}

private func animationProgress(at date: Date) -> Double {
let duration = 1.45
let cycleDuration = duration * 2
let elapsed = date.timeIntervalSinceReferenceDate.truncatingRemainder(dividingBy: cycleDuration)
let linearProgress = elapsed <= duration ? elapsed / duration : (cycleDuration - elapsed) / duration

return (1 - cos(linearProgress * .pi)) / 2
}

private func interpolate(from start: CGFloat, to end: CGFloat, progress: Double) -> CGFloat {
start + (end - start) * CGFloat(progress)
}
}
114 changes: 114 additions & 0 deletions iOSClient/Settings/AutoUpload/NCFocusedAutoUploadIntroView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-FileCopyrightText: Nextcloud GmbH
// SPDX-FileCopyrightText: 2026 Milen Pivchev
// SPDX-License-Identifier: GPL-3.0-or-later

import SwiftUI

struct NCFocusedAutoUploadIntroView: View {
@Environment(\.dismiss) private var dismiss

let onEnable: () -> Void

var body: some View {
VStack(spacing: 0) {
ZStack {
Text(NSLocalizedString("_focused_auto_upload_", comment: ""))
.font(.title3)
.fontWeight(.semibold)
.multilineTextAlignment(.center)

HStack {
Button {
dismiss()
} label: {
Image(systemName: "xmark")
.font(.system(size: 28, weight: .regular))
.foregroundStyle(.primary)
.frame(width: 48, height: 48)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityLabel(NSLocalizedString("_close_", comment: ""))

Spacer()
}
}
.padding(.top, 24)
.padding(.horizontal, 24)

Spacer(minLength: 90)

VStack(spacing: 24) {
Text(NSLocalizedString("_focused_auto_upload_intro_heading_", comment: ""))
.font(.title2)
.fontWeight(.semibold)
.multilineTextAlignment(.center)
.foregroundStyle(.primary)

Text(NSLocalizedString("_focused_auto_upload_intro_message_", comment: ""))
.font(.body)
.multilineTextAlignment(.center)
.foregroundStyle(.primary)
.fixedSize(horizontal: false, vertical: true)

VStack(alignment: .leading, spacing: 22) {
guidanceRow(systemImage: "wifi", textKey: "_focused_auto_upload_wifi_")
guidanceRow(systemImage: "battery.100", textKey: "_focused_auto_upload_charger_")
guidanceRow(systemImage: "logo", textKey: "_focused_auto_upload_do_not_exit_")
}
.padding(.top, 10)
}
.padding(.horizontal, 36)

Spacer(minLength: 80)

Button {
onEnable()
} label: {
Text(NSLocalizedString("_enable_focused_auto_upload_", comment: ""))
.font(.title3)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 18)
.contentShape(Capsule())
}
.buttonStyle(.plain)
.background(
Capsule()
.fill(Color(UIColor.darkGray))
)
.padding(.horizontal, 38)
.padding(.bottom, 28)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(UIColor.systemBackground))
}

private func guidanceRow(systemImage: String, textKey: String) -> some View {
HStack(spacing: 16) {
guidanceIcon(systemImage: systemImage)

Text(NSLocalizedString(textKey, comment: ""))
.font(.title3)
.foregroundStyle(.primary)
.fixedSize(horizontal: false, vertical: true)
}
}

@ViewBuilder
private func guidanceIcon(systemImage: String) -> some View {
if systemImage == "logo" {
Image("logo")
.renderingMode(.template)
.resizable()
.scaledToFit()
.foregroundStyle(.secondary)
.frame(width: 34, height: 28)
} else {
Image(systemName: systemImage)
.font(.system(size: 28, weight: .regular))
.foregroundStyle(.secondary)
.frame(width: 34)
}
}
}
Loading
Loading