Commit 1dab2a3e20
Unsigned
Layout: unified · split
gitbay/Views/Orgs/TeamView.swift +34 −2
| @@ -7,6 +7,8 @@ struct TeamView: View { | |||
| 7 | @State private var newMember = "" | 7 | @State private var newMember = "" |
| 8 | @State private var grantRepo = "" | 8 | @State private var grantRepo = "" |
| 9 | @State private var grantRole = "read" | 9 | @State private var grantRole = "read" |
| 10 | @State private var removingMember: String? | ||
| 11 | @State private var revokingGrant: TeamViewModel.Grant? | ||
| 10 | 12 | ||
| 11 | init(client: GitbayClient, org: String, team: String) { | 13 | init(client: GitbayClient, org: String, team: String) { |
| 12 | _model = State(initialValue: TeamViewModel(client: client, orgName: org, teamName: team)) | 14 | _model = State(initialValue: TeamViewModel(client: client, orgName: org, teamName: team)) |
| @@ -27,7 +29,7 @@ struct TeamView: View { | |||
| 27 | .font(.gbSans(.subheadline)) | 29 | .font(.gbSans(.subheadline)) |
| 28 | .swipeActions { | 30 | .swipeActions { |
| 29 | Button("Remove", role: .destructive) { | 31 | Button("Remove", role: .destructive) { |
| 30 | Task { await model.removeMember(user) } | 32 | removingMember = user |
| 31 | } | 33 | } |
| 32 | } | 34 | } |
| 33 | } | 35 | } |
| @@ -58,7 +60,7 @@ struct TeamView: View { | |||
| 58 | } | 60 | } |
| 59 | .swipeActions { | 61 | .swipeActions { |
| 60 | Button("Revoke", role: .destructive) { | 62 | Button("Revoke", role: .destructive) { |
| 61 | Task { await model.revoke(grant) } | 63 | revokingGrant = grant |
| 62 | } | 64 | } |
| 63 | } | 65 | } |
| 64 | } | 66 | } |
| @@ -92,6 +94,36 @@ struct TeamView: View { | |||
| 92 | .navigationTitle(model.teamName) | 94 | .navigationTitle(model.teamName) |
| 93 | .navigationBarTitleDisplayMode(.inline) | 95 | .navigationBarTitleDisplayMode(.inline) |
| 94 | .task { await model.load() } | 96 | .task { await model.load() } |
| 97 | .confirmationDialog( | ||
| 98 | "Remove \(removingMember ?? "") from \(model.teamName)?", | ||
| 99 | isPresented: Binding( | ||
| 100 | get: { removingMember != nil }, | ||
| 101 | set: { if !$0 { removingMember = nil } } | ||
| 102 | ) | ||
| 103 | ) { | ||
| 104 | Button("Remove", role: .destructive) { | ||
| 105 | if let user = removingMember { | ||
| 106 | Task { await model.removeMember(user) } | ||
| 107 | } | ||
| 108 | removingMember = nil | ||
| 109 | } | ||
| 110 | Button("Cancel", role: .cancel) {} | ||
| 111 | } | ||
| 112 | .confirmationDialog( | ||
| 113 | "Revoke \(model.teamName)'s \(revokingGrant?.role ?? "") access to \(revokingGrant?.repo ?? "")?", | ||
| 114 | isPresented: Binding( | ||
| 115 | get: { revokingGrant != nil }, | ||
| 116 | set: { if !$0 { revokingGrant = nil } } | ||
| 117 | ) | ||
| 118 | ) { | ||
| 119 | Button("Revoke", role: .destructive) { | ||
| 120 | if let grant = revokingGrant { | ||
| 121 | Task { await model.revoke(grant) } | ||
| 122 | } | ||
| 123 | revokingGrant = nil | ||
| 124 | } | ||
| 125 | Button("Cancel", role: .cancel) {} | ||
| 126 | } | ||
| 95 | .refreshable { await model.load() } | 127 | .refreshable { await model.load() } |
| 96 | } | 128 | } |
| 97 | } | 129 | } |
gitbay/Views/Repos/RepoSettingsView.swift +17 −1
| @@ -10,6 +10,7 @@ struct RepoSettingsView: View { | |||
| 10 | @State private var newTopic = "" | 10 | @State private var newTopic = "" |
| 11 | @State private var newBranch = "" | 11 | @State private var newBranch = "" |
| 12 | @State private var loadedOnce = false | 12 | @State private var loadedOnce = false |
| 13 | @State private var removingTopic: String? | ||
| 13 | 14 | ||
| 14 | init(client: GitbayClient, repo: String) { | 15 | init(client: GitbayClient, repo: String) { |
| 15 | _model = State(initialValue: RepoSettingsViewModel(client: client, repoPath: repo)) | 16 | _model = State(initialValue: RepoSettingsViewModel(client: client, repoPath: repo)) |
| @@ -48,6 +49,21 @@ struct RepoSettingsView: View { | |||
| 48 | await model.load() | 49 | await model.load() |
| 49 | await model.loadDeps() | 50 | await model.loadDeps() |
| 50 | } | 51 | } |
| 52 | .confirmationDialog( | ||
| 53 | "Remove topic \(removingTopic ?? "")?", | ||
| 54 | isPresented: Binding( | ||
| 55 | get: { removingTopic != nil }, | ||
| 56 | set: { if !$0 { removingTopic = nil } } | ||
| 57 | ) | ||
| 58 | ) { | ||
| 59 | Button("Remove", role: .destructive) { | ||
| 60 | if let topic = removingTopic { | ||
| 61 | Task { await model.removeTopic(topic) } | ||
| 62 | } | ||
| 63 | removingTopic = nil | ||
| 64 | } | ||
| 65 | Button("Cancel", role: .cancel) {} | ||
| 66 | } | ||
| 51 | } | 67 | } |
| 52 | 68 | ||
| 53 | // MARK: - Sections | 69 | // MARK: - Sections |
| @@ -93,7 +109,7 @@ struct RepoSettingsView: View { | |||
| 93 | HStack(spacing: 3) { | 109 | HStack(spacing: 3) { |
| 94 | Text(topic) | 110 | Text(topic) |
| 95 | Button { | 111 | Button { |
| 96 | Task { await model.removeTopic(topic) } | 112 | removingTopic = topic |
| 97 | } label: { | 113 | } label: { |
| 98 | Image(systemName: "xmark.circle.fill") | 114 | Image(systemName: "xmark.circle.fill") |
| 99 | .foregroundStyle(.tertiary) | 115 | .foregroundStyle(.tertiary) |
gitbayUITests/LiveSmokeUITests.swift +9 −1
| @@ -450,8 +450,12 @@ extension LiveSmokeUITests { | |||
| 450 | .firstMatch.tap() | 450 | .firstMatch.tap() |
| 451 | let chip = app.staticTexts["ios"].firstMatch | 451 | let chip = app.staticTexts["ios"].firstMatch |
| 452 | XCTAssertTrue(chip.waitForExistence(timeout: 10), "topic did not appear") | 452 | XCTAssertTrue(chip.waitForExistence(timeout: 10), "topic did not appear") |
| 453 | // Remove it again: the chip's own x button is the next button. | 453 | // Remove it again: the chip's own x button is the next button, |
| 454 | // and it asks before removing. | ||
| 454 | app.scrollViews.buttons.firstMatch.tap() | 455 | app.scrollViews.buttons.firstMatch.tap() |
| 456 | let confirmRemove = app.buttons["Remove"].firstMatch | ||
| 457 | XCTAssertTrue(confirmRemove.waitForExistence(timeout: 5), "topic remove confirmation missing") | ||
| 458 | confirmRemove.tap() | ||
| 455 | XCTAssertTrue(waitForDisappearance(chip, timeout: 10), "topic did not remove") | 459 | XCTAssertTrue(waitForDisappearance(chip, timeout: 10), "topic did not remove") |
| 456 | 460 | ||
| 457 | let resolved = app.switches["Require threads resolved"].firstMatch | 461 | let resolved = app.switches["Require threads resolved"].firstMatch |
| @@ -684,6 +688,10 @@ extension LiveSmokeUITests { | |||
| 684 | XCTAssertTrue(grantRow.waitForExistence(timeout: 15), "grant not listed") | 688 | XCTAssertTrue(grantRow.waitForExistence(timeout: 15), "grant not listed") |
| 685 | grantRow.swipeLeft() | 689 | grantRow.swipeLeft() |
| 686 | app.buttons["Revoke"].firstMatch.tap() | 690 | app.buttons["Revoke"].firstMatch.tap() |
| 691 | // The confirmation dialog's destructive Revoke. | ||
| 692 | let confirmRevoke = app.buttons["Revoke"].firstMatch | ||
| 693 | XCTAssertTrue(confirmRevoke.waitForExistence(timeout: 5), "revoke confirmation missing") | ||
| 694 | confirmRevoke.tap() | ||
| 687 | XCTAssertTrue(waitForDisappearance(grantRow, timeout: 15), "grant not revoked") | 695 | XCTAssertTrue(waitForDisappearance(grantRow, timeout: 15), "grant not revoked") |
| 688 | 696 | ||
| 689 | back() | 697 | back() |