Merge spinner + swipeable PR sections; bump to 3.1
Build iOS App / Build .ipa and publish source (push) Successful in 25s
Build iOS App / Build .ipa and publish source (push) Successful in 25s
- MergeSheet: show a spinner in the toolbar while the merge is confirming/ rejecting, lock Cancel and interactive dismissal during the request, and prevent double-taps. - PR detail: replace the ScrollView-switch with a page-style TabView so you can swipe left/right to change sections (iOS 26 style); bottom bar taps still select. Make DetailTab Hashable. Bump to 3.1.
This commit is contained in:
1 parent
2301b8a861
commit
ed36ed72de
3 files changed
+32
-12
No files matched your search
@@ -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 = 21;
|
CURRENT_PROJECT_VERSION = 22;
|
||||||
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 = 3.0;
|
MARKETING_VERSION = 3.1;
|
||||||
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 = 21;
|
CURRENT_PROJECT_VERSION = 22;
|
||||||
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 = 3.0;
|
MARKETING_VERSION = 3.1;
|
||||||
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;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ struct PRDetailView: View {
|
|||||||
@State private var requestChangesMode = false
|
@State private var requestChangesMode = false
|
||||||
@State private var showMergeSuccess = false
|
@State private var showMergeSuccess = false
|
||||||
|
|
||||||
enum DetailTab: String, CaseIterable, Identifiable {
|
enum DetailTab: String, CaseIterable, Identifiable, Hashable {
|
||||||
case overview = "Overview"
|
case overview = "Overview"
|
||||||
case conversation = "Conversation"
|
case conversation = "Conversation"
|
||||||
case files = "Files"
|
case files = "Files"
|
||||||
@@ -103,14 +103,20 @@ struct PRDetailView: View {
|
|||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
header(pr)
|
header(pr)
|
||||||
|
|
||||||
ScrollView {
|
// Page-style TabView gives iOS-native swipe-to-change between
|
||||||
switch selectedTab {
|
// sections (matching iOS 26), while the bottom bar taps select.
|
||||||
case .overview: OverviewTab(pr: pr)
|
TabView(selection: $selectedTab) {
|
||||||
case .conversation: ConversationTab(vm: vm)
|
OverviewTab(pr: pr)
|
||||||
case .files: FilesTab(vm: vm)
|
.tag(DetailTab.overview)
|
||||||
case .checks: ChecksTab(vm: vm, onRerun: { showRerunConfirm = $0 })
|
ConversationTab(vm: vm)
|
||||||
}
|
.tag(DetailTab.conversation)
|
||||||
|
FilesTab(vm: vm)
|
||||||
|
.tag(DetailTab.files)
|
||||||
|
ChecksTab(vm: vm, onRerun: { showRerunConfirm = $0 })
|
||||||
|
.tag(DetailTab.checks)
|
||||||
}
|
}
|
||||||
|
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||||
|
.ignoresSafeArea(edges: .bottom)
|
||||||
|
|
||||||
bottomTabBar
|
bottomTabBar
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ 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"
|
||||||
|
@State private var isMerging = false
|
||||||
/// Called when the merge succeeds, so the parent can show the success
|
/// Called when the merge succeeds, so the parent can show the success
|
||||||
/// animation and pop back to the landing page.
|
/// animation and pop back to the landing page.
|
||||||
var onSuccess: () -> Void = {}
|
var onSuccess: () -> Void = {}
|
||||||
@@ -118,20 +119,33 @@ struct MergeSheet: View {
|
|||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
|
if isMerging {
|
||||||
|
// No cancel while a merge is in flight.
|
||||||
|
} else {
|
||||||
Button("Cancel") { dismiss() }
|
Button("Cancel") { dismiss() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ToolbarItem(placement: .confirmationAction) {
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
|
if isMerging {
|
||||||
|
// Spinner while the merge is confirming/rejecting.
|
||||||
|
ProgressView()
|
||||||
|
} else {
|
||||||
Button("Merge") { performMerge() }
|
Button("Merge") { performMerge() }
|
||||||
.disabled(SettingsStore.shared.mergeGateMode == .block
|
.disabled(SettingsStore.shared.mergeGateMode == .block
|
||||||
&& vm.ciStatus == .failure)
|
&& vm.ciStatus == .failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.interactiveDismissDisabled(isMerging)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func performMerge() {
|
private func performMerge() {
|
||||||
|
guard !isMerging else { return }
|
||||||
|
isMerging = true
|
||||||
Task {
|
Task {
|
||||||
let success = await vm.merge(method: method)
|
let success = await vm.merge(method: method)
|
||||||
|
isMerging = false
|
||||||
if success {
|
if success {
|
||||||
dismiss()
|
dismiss()
|
||||||
onSuccess()
|
onSuccess()
|
||||||
|
|||||||
Reference in new issue
Block a user