Merge success animation + auto-return to landing page; bump to 3.0
Build iOS App / Build .ipa and publish source (push) Successful in 23s

On a successful merge, show a brief full-screen green checkmark animation
('Merged'), then automatically pop the PR detail view back to the landing
page. MergeSheet now calls an onSuccess callback; PRDetailView shows the
overlay and dismisses after ~1.2s. Bump to 3.0.
This commit is contained in:
Hermes committed 2026-08-13 16:35:06 +00:00
1 parent 5e9ee9e5cf
commit 2301b8a861
3 files changed
+47 -6

No files matched your search

+4 -4
View File
@@ -187,7 +187,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 20; CURRENT_PROJECT_VERSION = 21;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR"; INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR";
@@ -197,7 +197,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 2.9; MARKETING_VERSION = 3.0;
PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr; PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -211,7 +211,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 20; CURRENT_PROJECT_VERSION = 21;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR"; INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR";
@@ -221,7 +221,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 2.9; MARKETING_VERSION = 3.0;
PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr; PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
+36 -1
View File
@@ -2,12 +2,14 @@ import SwiftUI
struct PRDetailView: View { struct PRDetailView: View {
@StateObject private var vm: PRDetailViewModel @StateObject private var vm: PRDetailViewModel
@Environment(\.dismiss) private var dismiss
@State private var selectedTab: DetailTab = .overview @State private var selectedTab: DetailTab = .overview
@State private var showCommentSheet = false @State private var showCommentSheet = false
@State private var showMergeSheet = false @State private var showMergeSheet = false
@State private var showCloseConfirm = false @State private var showCloseConfirm = false
@State private var showRerunConfirm: ActionRun? @State private var showRerunConfirm: ActionRun?
@State private var requestChangesMode = false @State private var requestChangesMode = false
@State private var showMergeSuccess = false
enum DetailTab: String, CaseIterable, Identifiable { enum DetailTab: String, CaseIterable, Identifiable {
case overview = "Overview" case overview = "Overview"
@@ -56,6 +58,7 @@ struct PRDetailView: View {
Button("OK", role: .cancel) {} Button("OK", role: .cancel) {}
} message: { Text(vm.errorMessage ?? "") } } message: { Text(vm.errorMessage ?? "") }
.overlay { if vm.busyMessage != nil { busyOverlay } } .overlay { if vm.busyMessage != nil { busyOverlay } }
.overlay { if showMergeSuccess { mergeSuccessOverlay } }
.confirmationDialog("Close this PR?", isPresented: $showCloseConfirm, titleVisibility: .visible) { .confirmationDialog("Close this PR?", isPresented: $showCloseConfirm, titleVisibility: .visible) {
Button("Close", role: .destructive) { Task { await vm.setState("closed") } } Button("Close", role: .destructive) { Task { await vm.setState("closed") } }
Button("Cancel", role: .cancel) {} Button("Cancel", role: .cancel) {}
@@ -65,7 +68,19 @@ struct PRDetailView: View {
mode: requestChangesMode ? .requestChanges : .comment) mode: requestChangesMode ? .requestChanges : .comment)
.onDisappear { requestChangesMode = false } .onDisappear { requestChangesMode = false }
} }
.sheet(isPresented: $showMergeSheet) { MergeSheet(vm: vm) } .sheet(isPresented: $showMergeSheet) {
MergeSheet(vm: vm) {
// Merge succeeded: show the success animation, then pop back
// to the landing page.
withAnimation(.spring(response: 0.4, dampingFraction: 0.6)) {
showMergeSuccess = true
}
Task {
try? await Task.sleep(nanoseconds: 1_200_000_000)
dismiss()
}
}
}
.confirmationDialog("Re-run workflow?", .confirmationDialog("Re-run workflow?",
isPresented: .init( isPresented: .init(
get: { showRerunConfirm != nil }, get: { showRerunConfirm != nil },
@@ -219,4 +234,24 @@ struct PRDetailView: View {
} }
.ignoresSafeArea() .ignoresSafeArea()
} }
/// Full-screen success animation shown briefly after a merge, then the
/// view pops back to the landing page.
private var mergeSuccessOverlay: some View {
ZStack {
Color.black.opacity(0.4)
.ignoresSafeArea()
VStack(spacing: 12) {
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 72))
.foregroundColor(.green)
.scaleEffect(showMergeSuccess ? 1 : 0.3)
.opacity(showMergeSuccess ? 1 : 0)
Text("Merged")
.font(.title2.weight(.semibold))
.foregroundColor(.white)
}
}
.transition(.opacity)
}
} }
+7 -1
View File
@@ -65,6 +65,9 @@ struct MergeSheet: View {
@ObservedObject var vm: PRDetailViewModel @ObservedObject var vm: PRDetailViewModel
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@State private var method = "merge" @State private var method = "merge"
/// Called when the merge succeeds, so the parent can show the success
/// animation and pop back to the landing page.
var onSuccess: () -> Void = {}
private let methods = [("merge", "Create merge commit"), private let methods = [("merge", "Create merge commit"),
("rebase", "Rebase and merge"), ("rebase", "Rebase and merge"),
@@ -129,7 +132,10 @@ struct MergeSheet: View {
private func performMerge() { private func performMerge() {
Task { Task {
let success = await vm.merge(method: method) let success = await vm.merge(method: method)
if success { dismiss() } if success {
dismiss()
onSuccess()
}
} }
} }
} }