a native ios client for gitbay

client ios swift

https://gitbay.org

gitbay/Views/Orgs/TeamView.swift

97 lines · 4033 bytes

 1import SwiftUI
 2
 3/// One team: its members and its repo grants.
 4struct TeamView: View {
 5
 6    @State private var model: TeamViewModel
 7    @State private var newMember = ""
 8    @State private var grantRepo = ""
 9    @State private var grantRole = "read"
10
11    init(client: GitbayClient, org: String, team: String) {
12        _model = State(initialValue: TeamViewModel(client: client, orgName: org, teamName: team))
13    }
14
15    var body: some View {
16        List {
17            if let loaded = model.state.value {
18                if let error = model.actionError {
19                    Section {
20                        GBNotice(error, .gbWarn)
21                    }
22                }
23
24                Section("Members") {
25                    ForEach(loaded.members ?? [], id: \.self) { user in
26                        Text(user)
27                            .font(.gbSans(.subheadline))
28                            .swipeActions {
29                                Button("Remove", role: .destructive) {
30                                    Task { await model.removeMember(user) }
31                                }
32                            }
33                    }
34                    HStack {
35                        TextField("Add org member", text: $newMember)
36                            .autocorrectionDisabled()
37                            .textInputAutocapitalization(.never)
38                        Button {
39                            let user = newMember.trimmingCharacters(in: .whitespaces)
40                            newMember = ""
41                            Task { await model.addMember(user) }
42                        } label: {
43                            Image(systemName: "plus.circle.fill")
44                        }
45                        .disabled(newMember.trimmingCharacters(in: .whitespaces).isEmpty
46                            || model.working)
47                    }
48                }
49
50                Section {
51                    ForEach(loaded.grants ?? []) { grant in
52                        HStack {
53                            Text(grant.repo)
54                                .font(.gbSans(.subheadline))
55                                .lineLimit(1)
56                            Spacer()
57                            GBChip(grant.role, .secondary)
58                        }
59                        .swipeActions {
60                            Button("Revoke", role: .destructive) {
61                                Task { await model.revoke(grant) }
62                            }
63                        }
64                    }
65                    VStack(spacing: 8) {
66                        TextField("org/repo", text: $grantRepo)
67                            .autocorrectionDisabled()
68                            .textInputAutocapitalization(.never)
69                        HStack {
70                            Picker("Role", selection: $grantRole) {
71                                Text("read").tag("read")
72                                Text("write").tag("write")
73                                Text("admin").tag("admin")
74                            }
75                            .pickerStyle(.segmented)
76                            Button("Grant") {
77                                let repo = grantRepo.trimmingCharacters(in: .whitespaces)
78                                grantRepo = ""
79                                Task { await model.grant(repo: repo, role: grantRole) }
80                            }
81                            .disabled(!grantRepo.contains("/") || model.working)
82                        }
83                    }
84                } header: {
85                    Text("Repository grants")
86                } footer: {
87                    Text("A grant gives every team member the role on that org repository.")
88                }
89            }
90        }
91        .overlay { LoadStateOverlay(state: model.state) }
92        .navigationTitle(model.teamName)
93        .navigationBarTitleDisplayMode(.inline)
94        .task { await model.load() }
95        .refreshable { await model.load() }
96    }
97}