Skip to content

Commit 3c9401d

Browse files
Alert Banner (#4009)
* wip Signed-off-by: Marino Faggiana <marino.faggiana@nextcloud.com>
1 parent 856f2da commit 3c9401d

3 files changed

Lines changed: 198 additions & 11 deletions

File tree

Nextcloud.xcodeproj/project.pbxproj

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@
292292
F72AD71128C24BBB006CB92D /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = F72AD71028C24BBB006CB92D /* NextcloudKit */; };
293293
F72AD71328C24BCC006CB92D /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = F72AD71228C24BCC006CB92D /* NextcloudKit */; };
294294
F72CA0572F5048C3002E2F06 /* UIApplication+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72CA0562F5048C3002E2F06 /* UIApplication+Extension.swift */; };
295+
F72CA05C2F5051DB002E2F06 /* AlertActionBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72CA05B2F5051DB002E2F06 /* AlertActionBannerView.swift */; };
295296
F72CD63A25C19EBF00F46F9A /* NCAutoUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72CD63925C19EBF00F46F9A /* NCAutoUpload.swift */; };
296297
F72D1007210B6882009C96B7 /* NCPushNotificationEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = F72D1005210B6882009C96B7 /* NCPushNotificationEncryption.m */; };
297298
F72D404923D2082500A97FD0 /* NCViewerNextcloudText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72D404823D2082500A97FD0 /* NCViewerNextcloudText.swift */; };
@@ -1347,6 +1348,7 @@
13471348
F72944F42A8424F800246839 /* NCEndToEndMetadataV1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCEndToEndMetadataV1.swift; sourceTree = "<group>"; };
13481349
F72A17D728B221E300F3F159 /* DashboardWidgetView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashboardWidgetView.swift; sourceTree = "<group>"; };
13491350
F72CA0562F5048C3002E2F06 /* UIApplication+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Extension.swift"; sourceTree = "<group>"; };
1351+
F72CA05B2F5051DB002E2F06 /* AlertActionBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertActionBannerView.swift; sourceTree = "<group>"; };
13501352
F72CD63925C19EBF00F46F9A /* NCAutoUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAutoUpload.swift; sourceTree = "<group>"; };
13511353
F72D1005210B6882009C96B7 /* NCPushNotificationEncryption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NCPushNotificationEncryption.m; sourceTree = "<group>"; };
13521354
F72D1006210B6882009C96B7 /* NCPushNotificationEncryption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NCPushNotificationEncryption.h; sourceTree = "<group>"; };
@@ -2240,6 +2242,7 @@
22402242
F70557BC2ED44F1800135623 /* Lucid Banner */ = {
22412243
isa = PBXGroup;
22422244
children = (
2245+
F72CA05B2F5051DB002E2F06 /* AlertActionBannerView.swift */,
22432246
F7DF7B3E2F1A2EE400514020 /* BannerView.swift */,
22442247
F714A1462ED84AF00050A43B /* HudBannerView.swift */,
22452248
F70557BB2ED44F1800135623 /* UploadBannerView.swift */,
@@ -4683,6 +4686,7 @@
46834686
F343A4B32A1E01FF00DDA874 /* PHAsset+Extension.swift in Sources */,
46844687
F70968A424212C4E00ED60E5 /* NCLivePhoto.swift in Sources */,
46854688
F7C30DFA291BCF790017149B /* NCNetworkingE2EECreateFolder.swift in Sources */,
4689+
F72CA05C2F5051DB002E2F06 /* AlertActionBannerView.swift in Sources */,
46864690
F722133B2D40EF9D002F7438 /* NCFilesNavigationController.swift in Sources */,
46874691
F7BC288026663F85004D46C5 /* NCViewCertificateDetails.swift in Sources */,
46884692
F78B87E92B62550800C65ADC /* NCMediaDownloadThumbnail.swift in Sources */,
@@ -5746,7 +5750,7 @@
57465750
GCC_WARN_UNUSED_FUNCTION = YES;
57475751
GCC_WARN_UNUSED_VARIABLE = YES;
57485752
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Nextcloud. All rights reserved.";
5749-
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
5753+
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
57505754
LD_RUNPATH_SEARCH_PATHS = (
57515755
"$(inherited)",
57525756
"@executable_path/Frameworks",
@@ -5809,7 +5813,7 @@
58095813
GCC_WARN_UNUSED_FUNCTION = YES;
58105814
GCC_WARN_UNUSED_VARIABLE = YES;
58115815
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Nextcloud. All rights reserved.";
5812-
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
5816+
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
58135817
LD_RUNPATH_SEARCH_PATHS = (
58145818
"$(inherited)",
58155819
"@executable_path/Frameworks",
@@ -5968,7 +5972,7 @@
59685972
isa = XCRemoteSwiftPackageReference;
59695973
repositoryURL = "https://github.com/marinofaggiana/LucidBanner";
59705974
requirement = {
5971-
branch = 0.6.0;
5975+
branch = main;
59725976
kind = branch;
59735977
};
59745978
};
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// SPDX-FileCopyrightText: Nextcloud GmbH
2+
// SPDX-FileCopyrightText: 2026 Marino Faggiana
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
import SwiftUI
6+
import LucidBanner
7+
8+
@MainActor
9+
func showAlertActionBannerView(scene: UIWindowScene?,
10+
title: String? = nil,
11+
subtitle: String? = nil,
12+
onConfirm: (() -> Void)? = nil) {
13+
let isPad = scene?.traitCollection.userInterfaceIdiom == .pad
14+
let horizontalLayout: LucidBanner.HorizontalLayout =
15+
isPad
16+
? .centered(width: 450)
17+
: .stretch(margins: 20)
18+
19+
let payload = LucidBannerPayload(
20+
title: title,
21+
subtitle: subtitle,
22+
vPosition: .top,
23+
horizontalLayout: horizontalLayout,
24+
swipeToDismiss: true
25+
)
26+
27+
LucidBanner.shared.show(scene: scene,
28+
payload: payload,
29+
policy: .replace) { _, _ in
30+
LucidBanner.shared.dismiss()
31+
} content: { state in
32+
AlertActionBannerView(
33+
state: state,
34+
onConfirm: {
35+
onConfirm?()
36+
LucidBanner.shared.dismiss()
37+
},
38+
onCancel: {
39+
LucidBanner.shared.dismiss()
40+
}
41+
)
42+
}
43+
44+
}
45+
46+
// MARK: - SwiftUI
47+
48+
struct AlertActionBannerView: View {
49+
@ObservedObject var state: LucidBannerState
50+
51+
let onConfirm: (() -> Void)?
52+
let onCancel: (() -> Void)?
53+
54+
init(
55+
state: LucidBannerState,
56+
onConfirm: (() -> Void)? = nil,
57+
onCancel: (() -> Void)? = nil
58+
) {
59+
self.state = state
60+
self.onConfirm = onConfirm
61+
self.onCancel = onCancel
62+
}
63+
64+
var body: some View {
65+
alertActionContainerView {
66+
VStack(spacing: 20) {
67+
// Title
68+
if let title = state.payload.title, !title.isEmpty {
69+
Text(title)
70+
.font(.headline.weight(.semibold))
71+
.foregroundStyle(state.payload.textColor)
72+
.multilineTextAlignment(.center)
73+
}
74+
75+
// Subtitle
76+
if let subtitle = state.payload.subtitle, !subtitle.isEmpty {
77+
Text(subtitle)
78+
.font(.subheadline)
79+
.foregroundStyle(state.payload.textColor)
80+
.multilineTextAlignment(.center)
81+
}
82+
83+
// Buttons
84+
HStack(spacing: 12) {
85+
Button("_cancel_") {
86+
onCancel?()
87+
}
88+
.padding(.vertical, 8)
89+
.frame(maxWidth: .infinity)
90+
.background(
91+
Capsule()
92+
.stroke(Color.secondary.opacity(0.5), lineWidth: 1)
93+
)
94+
.foregroundStyle(.primary)
95+
96+
Button("_ok_") {
97+
onConfirm?()
98+
}
99+
.padding(.vertical, 8)
100+
.frame(maxWidth: .infinity)
101+
.background(
102+
Capsule().fill(Color.accentColor)
103+
)
104+
.foregroundStyle(.white)
105+
}
106+
.frame(maxWidth: .infinity)
107+
}
108+
.padding(20)
109+
}
110+
}
111+
112+
// MARK: - Container
113+
114+
@ViewBuilder
115+
func alertActionContainerView<Content: View>(@ViewBuilder _ content: () -> Content) -> some View {
116+
let cornerRadius: CGFloat = 22
117+
let backgroundColor = Color(.systemBackground).opacity(0.9)
118+
119+
if #available(iOS 26, *) {
120+
content()
121+
.background(
122+
RoundedRectangle(cornerRadius: cornerRadius)
123+
.fill(backgroundColor)
124+
.id(backgroundColor)
125+
)
126+
.glassEffect(.clear, in: RoundedRectangle(cornerRadius: cornerRadius))
127+
.shadow(color: .black.opacity(0.5), radius: 10, x: 0, y: 4)
128+
} else {
129+
content()
130+
.background(
131+
RoundedRectangle(cornerRadius: cornerRadius)
132+
.fill(backgroundColor)
133+
.id(backgroundColor)
134+
)
135+
.background(
136+
RoundedRectangle(cornerRadius: cornerRadius)
137+
.fill(.ultraThinMaterial)
138+
)
139+
.overlay(
140+
RoundedRectangle(cornerRadius: cornerRadius)
141+
.stroke(backgroundColor, lineWidth: 0.6)
142+
.allowsHitTesting(false)
143+
)
144+
.shadow(color: .black.opacity(0.5), radius: 10, x: 0, y: 4)
145+
}
146+
}
147+
}
148+
149+
// MARK: - Preview
150+
151+
#Preview("Alert - Light") {
152+
153+
let payload = LucidBannerPayload(
154+
title: "Title ?",
155+
subtitle: "Subtitle.",
156+
stage: .warning,
157+
vPosition: .center,
158+
blocksTouches: true
159+
)
160+
161+
let state = LucidBannerState(payload: payload)
162+
163+
return ZStack {
164+
Color.black.opacity(0.15)
165+
.ignoresSafeArea()
166+
167+
AlertActionBannerView(
168+
state: state,
169+
onConfirm: {
170+
print("Confirm tapped")
171+
},
172+
onCancel: {
173+
print("Cancel tapped")
174+
}
175+
)
176+
.padding()
177+
}
178+
}

iOSClient/Login/NCLogin.swift

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import UIKit
88
import NextcloudKit
99
import SwiftUI
1010
import SafariServices
11+
import LucidBanner
1112

1213
class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate {
1314
@IBOutlet weak var imageBrand: UIImageView!
@@ -187,20 +188,24 @@ class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate {
187188
override func viewDidAppear(_ animated: Bool) {
188189
super.viewDidAppear(animated)
189190

190-
if self.shareAccounts != nil, let image = UIImage(systemName: "person.badge.plus")?.withTintColor(.white, renderingMode: .alwaysOriginal), let backgroundColor = NCBrandColor.shared.customer.lighter(by: 10) {
191+
if self.shareAccounts != nil {
191192
let title = String(format: NSLocalizedString("_apps_nextcloud_detect_", comment: ""), NCBrandOptions.shared.brand)
192-
let description = String(format: NSLocalizedString("_add_existing_account_", comment: ""), NCBrandOptions.shared.brand)
193+
let subtitle = String(format: NSLocalizedString("_add_existing_account_", comment: ""), NCBrandOptions.shared.brand)
193194

194-
/*
195-
NCContentPresenter().alertAction(image: image, contentModeImage: .scaleAspectFit, sizeImage: CGSize(width: 45, height: 45), backgroundColor: backgroundColor, textColor: textColor, title: title, description: description, textCancelButton: "_cancel_", textOkButton: "_ok_", attributes: EKAttributes.topFloat) { identifier in
196-
if identifier == "ok" {
197-
self.openShareAccountsViewController(nil)
198-
}
195+
showAlertActionBannerView(scene: view.window?.windowScene,
196+
title: title,
197+
subtitle: subtitle) {
198+
self.openShareAccountsViewController(nil)
199199
}
200-
*/
201200
}
202201
}
203202

203+
override func viewWillDisappear(_ animated: Bool) {
204+
super.viewWillDisappear(animated)
205+
206+
LucidBanner.shared.dismiss()
207+
}
208+
204209
private func handleLoginWithAppConfig() {
205210
let accountCount = NCManageDatabase.shared.getAccounts()?.count ?? 0
206211

0 commit comments

Comments
 (0)