Restore vertical scroll + line-based markdown; swipe on bottom bar; bump to 3.3
Build iOS App / Build .ipa and publish source (push) Failing after 16s
Build iOS App / Build .ipa and publish source (push) Failing after 16s
The horizontal paging ScrollView squished the markdown text and the TabView broke navigation. Revert to the plain vertical ScrollView + switch (always rendered correctly). Swipe-to-change now lives on the bottom tab bar itself (simultaneous DragGesture), so tapping tabs still works. Rewrite MarkdownText line-based: each source line renders on its own line (AttributedString collapses single newlines into spaces), with heading/bullet/code handling. Bump to 3.3.
This commit is contained in:
1 parent
221d23a6a3
commit
dd0fc84238
3 files changed
+78
-30
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 = 23;
|
CURRENT_PROJECT_VERSION = 24;
|
||||||
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.2;
|
MARKETING_VERSION = 3.3;
|
||||||
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 = 23;
|
CURRENT_PROJECT_VERSION = 24;
|
||||||
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.2;
|
MARKETING_VERSION = 3.3;
|
||||||
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;
|
||||||
|
|||||||
@@ -103,32 +103,17 @@ struct PRDetailView: View {
|
|||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
header(pr)
|
header(pr)
|
||||||
|
|
||||||
// Horizontal paging ScrollView — swipeable like a TabView, but
|
// Vertical scroll — has always rendered correctly (a horizontal
|
||||||
// without breaking NavigationLink inside the tabs (TabView's .page
|
// ScrollView/TabView broke NavigationLink and mangled markdown).
|
||||||
// style swallows taps and interferes with nested navigation).
|
ScrollView {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
switch selectedTab {
|
||||||
HStack(spacing: 0) {
|
case .overview: OverviewTab(pr: pr)
|
||||||
OverviewTab(pr: pr)
|
case .conversation: ConversationTab(vm: vm)
|
||||||
.id(DetailTab.overview)
|
case .files: FilesTab(vm: vm)
|
||||||
.containerRelativeFrame(.horizontal)
|
case .checks: ChecksTab(vm: vm, onRerun: { showRerunConfirm = $0 })
|
||||||
|
case .none: OverviewTab(pr: pr)
|
||||||
ConversationTab(vm: vm)
|
|
||||||
.id(DetailTab.conversation)
|
|
||||||
.containerRelativeFrame(.horizontal)
|
|
||||||
|
|
||||||
FilesTab(vm: vm)
|
|
||||||
.id(DetailTab.files)
|
|
||||||
.containerRelativeFrame(.horizontal)
|
|
||||||
|
|
||||||
ChecksTab(vm: vm, onRerun: { showRerunConfirm = $0 })
|
|
||||||
.id(DetailTab.checks)
|
|
||||||
.containerRelativeFrame(.horizontal)
|
|
||||||
}
|
}
|
||||||
.scrollTargetLayout()
|
|
||||||
}
|
}
|
||||||
.scrollTargetBehavior(.paging)
|
|
||||||
.scrollPosition(id: $selectedTab)
|
|
||||||
.ignoresSafeArea(edges: .bottom)
|
|
||||||
|
|
||||||
bottomTabBar
|
bottomTabBar
|
||||||
}
|
}
|
||||||
@@ -204,6 +189,21 @@ struct PRDetailView: View {
|
|||||||
)
|
)
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
.padding(.bottom, 8)
|
.padding(.bottom, 8)
|
||||||
|
// Swipe across the bar to change sections (iOS 26 feel). Taps on the
|
||||||
|
// individual tab buttons still work — simultaneous gesture.
|
||||||
|
.simultaneousGesture(
|
||||||
|
DragGesture(minimumDistance: 20)
|
||||||
|
.onEnded { value in
|
||||||
|
let tabs = DetailTab.allCases
|
||||||
|
guard let current = selectedTab ?? .overview,
|
||||||
|
let idx = tabs.firstIndex(of: current) else { return }
|
||||||
|
if value.translation.width < -40, idx < tabs.count - 1 {
|
||||||
|
withAnimation { selectedTab = tabs[idx + 1] }
|
||||||
|
} else if value.translation.width > 40, idx > 0 {
|
||||||
|
withAnimation { selectedTab = tabs[idx - 1] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var reviewMenu: some View {
|
private var reviewMenu: some View {
|
||||||
|
|||||||
@@ -167,6 +167,11 @@ struct OverviewTab: View {
|
|||||||
|
|
||||||
/// A Text view that renders a markdown string, falling back to plain text if
|
/// A Text view that renders a markdown string, falling back to plain text if
|
||||||
/// the markdown can't be parsed.
|
/// the markdown can't be parsed.
|
||||||
|
///
|
||||||
|
/// Line-based: each source line renders on its own line (AttributedString
|
||||||
|
/// collapses single newlines into spaces, which squishes PR bodies that use
|
||||||
|
/// one-line-per-key formatting). Handles headings, bullets, and code fences;
|
||||||
|
/// inline formatting (bold, italic, links, inline code) via AttributedString.
|
||||||
struct MarkdownText: View {
|
struct MarkdownText: View {
|
||||||
let markdown: String
|
let markdown: String
|
||||||
|
|
||||||
@@ -175,12 +180,55 @@ struct MarkdownText: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let attributed = try? AttributedString(markdown: markdown) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
|
let lines = markdown.components(separatedBy: "\n")
|
||||||
|
var inCodeBlock = false
|
||||||
|
ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
|
||||||
|
if line.hasPrefix("```") {
|
||||||
|
inCodeBlock.toggle()
|
||||||
|
} else if inCodeBlock {
|
||||||
|
Text(line)
|
||||||
|
.font(.caption.monospaced())
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding(4)
|
||||||
|
.background(Color(.secondarySystemBackground))
|
||||||
|
} else if line.hasPrefix("# ") || line.hasPrefix("## ") || line.hasPrefix("### ") {
|
||||||
|
heading(line)
|
||||||
|
} else if line.hasPrefix("- ") || line.hasPrefix("* ") {
|
||||||
|
bullet(line)
|
||||||
|
} else if line.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||||
|
Spacer(minLength: 4)
|
||||||
|
} else {
|
||||||
|
inline(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func heading(_ line: String) -> some View {
|
||||||
|
let level = line.prefix(while: { $0 == "#" }).count
|
||||||
|
let text = line.dropFirst(level).trimmingCharacters(in: .whitespaces)
|
||||||
|
return Text(text)
|
||||||
|
.font(level == 1 ? .title2.bold() : level == 2 ? .title3.bold() : .headline)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func bullet(_ line: String) -> some View {
|
||||||
|
HStack(alignment: .top, spacing: 6) {
|
||||||
|
Text("•")
|
||||||
|
inline(String(line.dropFirst(2)))
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func inline(_ text: String) -> some View {
|
||||||
|
if let attributed = try? AttributedString(markdown: text) {
|
||||||
Text(attributed)
|
Text(attributed)
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
} else {
|
} else {
|
||||||
Text(markdown)
|
Text(text)
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user