Skip to content

Commit 62cfd62

Browse files
Merge pull request #525 from PermanentOrg/feature/VSP-1611-iOS-Mobile-Link-Settings-Select-Role-and-Access
VSP-1611 iOS Mobile link settings select role and access
2 parents 09b4cb3 + f7a914c commit 62cfd62

File tree

10 files changed

+322
-53
lines changed

10 files changed

+322
-53
lines changed

Permanent.xcodeproj/project.pbxproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,8 @@
431431
5EC8C4B826D65006002A7ECC /* ArchivesEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EC8C4B726D65006002A7ECC /* ArchivesEndpoint.swift */; };
432432
5EC8C4BB26D6726B002A7ECC /* ArchiveScreenChooseArchiveDetailsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EC8C4B926D6726B002A7ECC /* ArchiveScreenChooseArchiveDetailsTableViewCell.swift */; };
433433
5EC8C4BC26D6726B002A7ECC /* ArchiveScreenChooseArchiveDetailsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5EC8C4BA26D6726B002A7ECC /* ArchiveScreenChooseArchiveDetailsTableViewCell.xib */; };
434+
5ECB0C912E856F15006EFDA7 /* RevokeBottomAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ECB0C902E856F15006EFDA7 /* RevokeBottomAlertView.swift */; };
435+
5ECB0C922E856F15006EFDA7 /* RevokeBottomAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ECB0C902E856F15006EFDA7 /* RevokeBottomAlertView.swift */; };
434436
5ECBAF9D2A1B5EEE00FACFDF /* ArchiveSteward.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ECBAF9C2A1B5EED00FACFDF /* ArchiveSteward.swift */; };
435437
5ECBAF9E2A1B5EF500FACFDF /* ArchiveSteward.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ECBAF9C2A1B5EED00FACFDF /* ArchiveSteward.swift */; };
436438
5ECBAFA32A1B640900FACFDF /* ArchiveStewardResponseTriggerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ECBAFA22A1B640900FACFDF /* ArchiveStewardResponseTriggerType.swift */; };
@@ -1393,6 +1395,7 @@
13931395
5EC8C4B726D65006002A7ECC /* ArchivesEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchivesEndpoint.swift; sourceTree = "<group>"; };
13941396
5EC8C4B926D6726B002A7ECC /* ArchiveScreenChooseArchiveDetailsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveScreenChooseArchiveDetailsTableViewCell.swift; sourceTree = "<group>"; };
13951397
5EC8C4BA26D6726B002A7ECC /* ArchiveScreenChooseArchiveDetailsTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ArchiveScreenChooseArchiveDetailsTableViewCell.xib; sourceTree = "<group>"; };
1398+
5ECB0C902E856F15006EFDA7 /* RevokeBottomAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RevokeBottomAlertView.swift; sourceTree = "<group>"; };
13961399
5ECBAF9C2A1B5EED00FACFDF /* ArchiveSteward.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArchiveSteward.swift; sourceTree = "<group>"; };
13971400
5ECBAFA22A1B640900FACFDF /* ArchiveStewardResponseTriggerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveStewardResponseTriggerType.swift; sourceTree = "<group>"; };
13981401
5ECBAFA62A1B731B00FACFDF /* LegacyPlanningArchiveDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyPlanningArchiveDetails.swift; sourceTree = "<group>"; };
@@ -3388,6 +3391,7 @@
33883391
5EA9C2B62E45FF78003BA1E9 /* Components */ = {
33893392
isa = PBXGroup;
33903393
children = (
3394+
5ECB0C902E856F15006EFDA7 /* RevokeBottomAlertView.swift */,
33913395
5E317C652E7C1D8600DE5A77 /* SelectableOptionView.swift */,
33923396
5E28C32B2E7BEECB00A073A8 /* SelectableOption.swift */,
33933397
5EA9C2B52E45FF78003BA1E9 /* ShareComponents.swift */,
@@ -3407,10 +3411,10 @@
34073411
isa = PBXGroup;
34083412
children = (
34093413
5E08C0D42E6F063600374D0F /* ShareContainerView.swift */,
3414+
5E08C0D32E6F063600374D0F /* LinkSettingsView.swift */,
34103415
5EAD21E12E73589500D15F76 /* RoleSelectionView.swift */,
34113416
5E98C9802E706A28002AEFA5 /* GeneralAccessView.swift */,
34123417
5EA9C2BE2E45FF78003BA1E9 /* ShareItemView.swift */,
3413-
5E08C0D32E6F063600374D0F /* LinkSettingsView.swift */,
34143418
5EA9C2B62E45FF78003BA1E9 /* Components */,
34153419
5EA9C2B82E45FF78003BA1E9 /* ViewModels */,
34163420
);
@@ -4993,6 +4997,7 @@
49934997
5ECBAF9D2A1B5EEE00FACFDF /* ArchiveSteward.swift in Sources */,
49944998
5E46217225C17C5A007642BE /* AccountInfoViewController.swift in Sources */,
49954999
5ED3B3AC29F7E008000CFF48 /* LegacyPlanningStewardViewController.swift in Sources */,
5000+
5ECB0C922E856F15006EFDA7 /* RevokeBottomAlertView.swift in Sources */,
49965001
5E5979CC2DC8CEBD00694BDA /* ChecklistBottomMenuView.swift in Sources */,
49975002
9220ACC92A1CAA72003797C9 /* SequenceExtension.swift in Sources */,
49985003
BC6358AF2536FD1700EEC48C /* TimezoneVO.swift in Sources */,
@@ -5670,6 +5675,7 @@
56705675
92E3FB5F2A176A5B00E9E5A6 /* LegacyAccountStatusCell.swift in Sources */,
56715676
F5A87F9A2996C51100C4272B /* WorkspacesContentViewModel.swift in Sources */,
56725677
5ED06F41286B2BD5006C126B /* ShareExtensionViewController.swift in Sources */,
5678+
5ECB0C912E856F15006EFDA7 /* RevokeBottomAlertView.swift in Sources */,
56735679
5E1CCC20287F050500913EEA /* CustomButton.swift in Sources */,
56745680
5ED4B9C72876DF3100CF044B /* Model.swift in Sources */,
56755681
F559F89D28FEEABA0015A522 /* APIPayload.swift in Sources */,

Permanent/Common/Network/ShareLinksV2Endpoint.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ enum ShareLinksV2Endpoint {
2323
expirationTimestamp: String? = nil
2424
)
2525
case getShareLink(shareLinkId: String)
26+
case deleteShareLink(shareLinkId: String)
2627
}
2728

2829
extension ShareLinksV2Endpoint: RequestProtocol {
@@ -45,6 +46,8 @@ extension ShareLinksV2Endpoint: RequestProtocol {
4546
)
4647
case .getShareLink:
4748
return nil
49+
case .deleteShareLink:
50+
return nil
4851
}
4952
}
5053

@@ -60,6 +63,8 @@ extension ShareLinksV2Endpoint: RequestProtocol {
6063
return .patch
6164
case .getShareLink:
6265
return .get
66+
case .deleteShareLink:
67+
return .delete
6368
}
6469
}
6570

@@ -71,6 +76,8 @@ extension ShareLinksV2Endpoint: RequestProtocol {
7176
return ["content-type": "application/json; charset=utf-8"]
7277
case .getShareLink:
7378
return nil
79+
case .deleteShareLink:
80+
return nil
7481
}
7582
}
7683

@@ -102,6 +109,8 @@ extension ShareLinksV2Endpoint: RequestProtocol {
102109
return "\(endpointPath)api/v2/share-links/\(shareLinkId)"
103110
case .getShareLink(let shareLinkId):
104111
return "\(endpointPath)api/v2/share-links?shareLinkIds[]=\(shareLinkId)"
112+
case .deleteShareLink(let shareLinkId):
113+
return "\(endpointPath)api/v2/share-links/\(shareLinkId)"
105114
}
106115
}
107116
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
//
2+
// RevokeBottomAlertView.swift
3+
// Permanent
4+
//
5+
// Created by Lucian Cerbu on 25.09.2025.
6+
//
7+
8+
import SwiftUI
9+
10+
struct RevokeBottomAlertView: View {
11+
@Binding var isPresented: Bool
12+
let onRevoke: () -> Void
13+
let onCancel: (() -> Void)?
14+
15+
init(
16+
isPresented: Binding<Bool>,
17+
onRevoke: @escaping () -> Void,
18+
onCancel: (() -> Void)? = nil
19+
) {
20+
self._isPresented = isPresented
21+
self.onRevoke = onRevoke
22+
self.onCancel = onCancel
23+
}
24+
25+
var body: some View {
26+
ZStack(alignment: .bottom) {
27+
Color.blue700
28+
.opacity(isPresented ? 0.5 : 0)
29+
.ignoresSafeArea()
30+
.animation(.easeOut(duration: 0.2), value: isPresented)
31+
.onTapGesture {
32+
withAnimation(.easeOut(duration: 0.3)) {
33+
isPresented = false
34+
}
35+
onCancel?()
36+
}
37+
38+
alertCard
39+
.offset(y: isPresented ? 0 : 400)
40+
.opacity(isPresented ? 1 : 0)
41+
.animation(.easeOut(duration: 0.3), value: isPresented)
42+
}
43+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
44+
.ignoresSafeArea()
45+
}
46+
47+
private var alertCard: some View {
48+
VStack {
49+
HStack {
50+
Spacer()
51+
Text("Are you sure you want to ")
52+
+ Text("revoke this share link?")
53+
.bold()
54+
Spacer()
55+
}
56+
.font(.custom("Usual-Regular", size: 14))
57+
.multilineTextAlignment(.center)
58+
.lineSpacing(6)
59+
.foregroundColor(.blue700)
60+
.padding(.top, 32)
61+
.padding(.horizontal, 32)
62+
63+
VStack(spacing: 16) {
64+
Button(action: revokeAction) {
65+
HStack {
66+
Spacer()
67+
Text("Revoke link")
68+
.fontWeight(.medium)
69+
.font(.custom("Usual-Regular", size: 14))
70+
.foregroundColor(.white)
71+
Spacer()
72+
}
73+
.padding(16)
74+
.frame(height: 56)
75+
.background(Color.error500)
76+
.cornerRadius(12)
77+
.shadow(color: Color.black.opacity(0.07), radius: 40, x: 0, y: 5)
78+
.frame(maxWidth: .infinity)
79+
}
80+
81+
Button(action: cancelAction) {
82+
HStack {
83+
Spacer()
84+
Text("Cancel")
85+
.font(.custom("Usual-Regular", size: 14))
86+
.fontWeight(.medium)
87+
.foregroundColor(.blue900)
88+
Spacer()
89+
}
90+
.padding(16)
91+
.frame(height: 56)
92+
.background(Color.blue50)
93+
.cornerRadius(12)
94+
.shadow(color: Color.black.opacity(0.07), radius: 40, x: 0, y: 5)
95+
.frame(maxWidth: .infinity)
96+
}
97+
}
98+
.padding(32)
99+
}
100+
.frame(maxWidth: .infinity)
101+
.background(Color(.white))
102+
.cornerRadius(12)
103+
.padding(.horizontal, 0)
104+
}
105+
106+
private func revokeAction() {
107+
if #available(iOS 17.0, *) {
108+
withAnimation(.easeOut(duration: 0.3)) {
109+
isPresented = false
110+
} completion: {
111+
onRevoke()
112+
}
113+
} else {
114+
withAnimation(.easeOut(duration: 0.3)) {
115+
isPresented = false
116+
}
117+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
118+
onRevoke()
119+
}
120+
}
121+
}
122+
123+
private func cancelAction() {
124+
withAnimation(.easeOut(duration: 0.3)) {
125+
isPresented = false
126+
}
127+
onCancel?()
128+
}
129+
}
130+
131+
#Preview {
132+
RevokeBottomAlertView(
133+
isPresented: .constant(true),
134+
onRevoke: {
135+
print("Revoke tapped")
136+
},
137+
onCancel: {
138+
print("Cancel tapped")
139+
}
140+
)
141+
}
142+
143+

Permanent/Modules/Shares/SwiftUIViews/GeneralAccessView.swift

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,17 @@ struct GeneralAccessView: View {
1212

1313
var body: some View {
1414
VStack(spacing: 0) {
15-
// Top bar
1615
topBar
1716
.padding(.horizontal, 16)
1817
.padding(.top, 12)
1918
.padding(.bottom, 8)
2019
.frame(height: 64)
2120
.background(Color.white)
22-
23-
// Content
21+
Rectangle()
22+
.foregroundColor(.clear)
23+
.frame(maxWidth: .infinity, minHeight: 1, maxHeight: 1)
24+
.background(Color.blue50)
2425
VStack(spacing: 0) {
25-
Rectangle()
26-
.foregroundColor(.clear)
27-
.frame(maxWidth: .infinity, minHeight: 1, maxHeight: 1)
28-
.background(Color.blue50)
29-
3026
ForEach(ShareViewAccessLevel.allCases, id: \.self) { accessLevel in
3127
SelectableOptionView(
3228
option: accessLevel,
@@ -36,7 +32,6 @@ struct GeneralAccessView: View {
3632
}
3733
)
3834
}
39-
4035
Spacer()
4136
}
4237
}
@@ -77,6 +72,7 @@ struct GeneralAccessView: View {
7772
HStack {
7873
Spacer()
7974
Button(action: {
75+
viewModel.revertChanges()
8076
viewModel.navigationDirection = .backward
8177
viewModel.showGeneralAccess = false
8278
viewModel.showLinkSettings = false

Permanent/Modules/Shares/SwiftUIViews/LinkSettingsView.swift

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,13 @@ struct LinkSettingsView: View {
2323
}
2424
.frame(height: 64)
2525
.background(Color.white)
26+
Rectangle()
27+
.foregroundColor(.clear)
28+
.frame(maxWidth: .infinity, minHeight: 1, maxHeight: 1)
29+
.background(Color.blue50)
2630

27-
// Scrollable content
2831
ScrollView(showsIndicators: false) {
29-
VStack(spacing: 0) {
30-
Rectangle()
31-
.foregroundColor(.clear)
32-
.frame(maxWidth: .infinity, minHeight: 1, maxHeight: 1)
33-
.background(Color.blue50)
32+
3433

3534
VStack(spacing: 20) {
3635
linkDisplaySection
@@ -44,7 +43,7 @@ struct LinkSettingsView: View {
4443
revokeLinkButton
4544
}
4645
.padding(.bottom, 120) // Height of buttons + padding + safe area
47-
}
46+
4847
}
4948
}
5049

@@ -78,13 +77,20 @@ struct LinkSettingsView: View {
7877
} message: {
7978
if let errorMessage = viewModel.errorMessage { Text(errorMessage) }
8079
}
80+
.overlay {
81+
RevokeBottomAlertView(
82+
isPresented: $viewModel.showRevokeAlert,
83+
onRevoke: viewModel.revokeAction
84+
)
85+
}
8186
}
8287

8388
// MARK: - Top Bar
8489
private var topBar: some View {
8590
ZStack {
8691
HStack {
8792
Button(action: {
93+
viewModel.revertChanges()
8894
viewModel.navigationDirection = .backward
8995
viewModel.showLinkSettings = false
9096
}) {
@@ -356,6 +362,7 @@ struct LinkSettingsView: View {
356362
// MARK: - Cancel Button
357363
private var cancelButton: some View {
358364
Button(action: {
365+
viewModel.revertChanges()
359366
viewModel.navigationDirection = .backward
360367
viewModel.showLinkSettings = false
361368
}) {

0 commit comments

Comments
 (0)