Merge icon, delete-branch-after-merge setting, floating tab bar; bump to 2.0
Build iOS App / Build .ipa and publish source (push) Successful in 22s
Build iOS App / Build .ipa and publish source (push) Successful in 22s
- Merge button: use the proper SF Symbol 'arrow.triangle.merge' instead of the download icon 'arrow.down.circle'. - Add a Settings toggle 'Delete branch after merge'. When on, the merge call passes delete_branch_after_merge=true and the head branch is deleted after a successful merge (best-effort). - PR detail bottom tab bar: floating capsule style (regularMaterial + shadow) like the latest iOS tab bars, instead of a full-width bar. - Bump to 2.0.
This commit is contained in:
@@ -187,7 +187,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 10;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR";
|
||||
@@ -197,7 +197,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.9;
|
||||
MARKETING_VERSION = 2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
@@ -211,7 +211,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 10;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR";
|
||||
@@ -221,7 +221,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.9;
|
||||
MARKETING_VERSION = 2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
|
||||
@@ -131,7 +131,9 @@ struct PRDetailView: View {
|
||||
.background(Color(.secondarySystemBackground))
|
||||
}
|
||||
|
||||
/// Bottom tab bar: icon-only navigation between the PR's sections.
|
||||
/// Bottom tab bar: icon-only navigation between the PR's sections, in a
|
||||
/// floating capsule style (like the latest iOS tab bars) rather than a
|
||||
/// full-width bar.
|
||||
private var bottomTabBar: some View {
|
||||
HStack {
|
||||
ForEach(DetailTab.allCases) { tab in
|
||||
@@ -149,8 +151,15 @@ struct PRDetailView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 6)
|
||||
.background(.bar)
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 6)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(.regularMaterial)
|
||||
.shadow(color: .black.opacity(0.15), radius: 8, y: 2)
|
||||
)
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
|
||||
private var reviewMenu: some View {
|
||||
@@ -173,7 +182,7 @@ struct PRDetailView: View {
|
||||
Button {
|
||||
showMergeSheet = true
|
||||
} label: {
|
||||
Image(systemName: "arrow.down.circle")
|
||||
Image(systemName: "arrow.triangle.merge")
|
||||
}
|
||||
.disabled(vm.ciStatus == .failure &&
|
||||
SettingsStore.shared.mergeGateMode == .block)
|
||||
|
||||
@@ -130,8 +130,16 @@ final class PRDetailViewModel: ObservableObject {
|
||||
busyMessage = "Merging…"
|
||||
defer { busyMessage = nil }
|
||||
do {
|
||||
let deleteBranch = SettingsStore.shared.deleteBranchAfterMerge
|
||||
try await client.merge(owner: owner, repo: repo.name,
|
||||
number: prNumber, method: method)
|
||||
number: prNumber, method: method,
|
||||
deleteBranchAfterMerge: deleteBranch)
|
||||
// If the setting is on, delete the head branch after a successful
|
||||
// merge (best-effort; the branch may already be gone or protected).
|
||||
if deleteBranch, let head = pr?.head?.ref {
|
||||
try? await client.deleteBranch(owner: owner, repo: repo.name,
|
||||
branch: head)
|
||||
}
|
||||
// Refresh the PR state to reflect the merge.
|
||||
pr = try? await client.pullRequest(owner: owner, repo: repo.name,
|
||||
number: prNumber)
|
||||
|
||||
@@ -211,12 +211,23 @@ struct GiteaClient {
|
||||
}
|
||||
|
||||
func merge(owner: String, repo: String, number: Int,
|
||||
method: String) async throws {
|
||||
struct Body: Encodable { let Do: String } // 'Do' = merge method: merge|rebase|squash
|
||||
method: String, deleteBranchAfterMerge: Bool) async throws {
|
||||
struct Body: Encodable {
|
||||
let Do: String // merge|rebase|squash
|
||||
let delete_branch_after_merge: Bool
|
||||
}
|
||||
_ = try await request(
|
||||
"repos/\(owner)/\(repo)/pulls/\(number)/merge",
|
||||
method: "POST",
|
||||
body: try JSONEncoder().encode(Body(Do: method)))
|
||||
body: try JSONEncoder().encode(Body(Do: method,
|
||||
delete_branch_after_merge: deleteBranchAfterMerge)))
|
||||
}
|
||||
|
||||
/// Delete a branch (used after merging when the setting is enabled).
|
||||
func deleteBranch(owner: String, repo: String, branch: String) async throws {
|
||||
let encoded = branch.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? branch
|
||||
_ = try await request("repos/\(owner)/\(repo)/branches/\(encoded)",
|
||||
method: "DELETE")
|
||||
}
|
||||
|
||||
func setState(owner: String, repo: String, number: Int,
|
||||
|
||||
@@ -23,12 +23,17 @@ final class SettingsStore: ObservableObject {
|
||||
didSet { UserDefaults.standard.set(mergeGateMode.rawValue,
|
||||
forKey: "mergeGateMode") }
|
||||
}
|
||||
@Published var deleteBranchAfterMerge: Bool {
|
||||
didSet { UserDefaults.standard.set(deleteBranchAfterMerge,
|
||||
forKey: "deleteBranchAfterMerge") }
|
||||
}
|
||||
|
||||
private init() {
|
||||
instanceURL = KeychainHelper.get("instanceURL") ?? "https://gitea.bueso.eu"
|
||||
token = KeychainHelper.get("token") ?? ""
|
||||
let raw = UserDefaults.standard.string(forKey: "mergeGateMode")
|
||||
mergeGateMode = MergeGateMode(rawValue: raw ?? "") ?? .warn
|
||||
deleteBranchAfterMerge = UserDefaults.standard.bool(forKey: "deleteBranchAfterMerge")
|
||||
}
|
||||
|
||||
var isConfigured: Bool {
|
||||
|
||||
@@ -5,6 +5,7 @@ struct SettingsView: View {
|
||||
@State private var instanceURL: String
|
||||
@State private var token: String
|
||||
@State private var mergeGateMode: MergeGateMode
|
||||
@State private var deleteBranchAfterMerge: Bool
|
||||
@State private var testResult: TestResult?
|
||||
@State private var testing = false
|
||||
@State private var showLogoutConfirm = false
|
||||
@@ -18,6 +19,7 @@ struct SettingsView: View {
|
||||
_instanceURL = State(initialValue: SettingsStore.shared.instanceURL)
|
||||
_token = State(initialValue: SettingsStore.shared.token)
|
||||
_mergeGateMode = State(initialValue: SettingsStore.shared.mergeGateMode)
|
||||
_deleteBranchAfterMerge = State(initialValue: SettingsStore.shared.deleteBranchAfterMerge)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -49,6 +51,7 @@ struct SettingsView: View {
|
||||
Text("Warn mode shows CI status and allows merging a red PR after confirmation. Block mode disables merge while any check is failing.")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
Toggle("Delete branch after merge", isOn: $deleteBranchAfterMerge)
|
||||
}
|
||||
|
||||
Section("Diagnostics") {
|
||||
@@ -130,6 +133,7 @@ struct SettingsView: View {
|
||||
instanceURL.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
SettingsStore.shared.token = token
|
||||
SettingsStore.shared.mergeGateMode = mergeGateMode
|
||||
SettingsStore.shared.deleteBranchAfterMerge = deleteBranchAfterMerge
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user