refine iOS swipe surface reveal

This commit is contained in:
2026-04-10 12:12:12 +02:00
parent 190d5833b0
commit c2fd98811a
3 changed files with 46 additions and 38 deletions
+43 -35
View File
@@ -47,39 +47,10 @@ struct RoundView: View {
)
ZStack {
HermesTheme.surface
.ignoresSafeArea()
if round != nil {
HermesVideoPlayerView(coordinator: playerCoordinator, cornerRadius: 0, fixedHeight: nil)
.ignoresSafeArea()
}
LinearGradient(
colors: [.black.opacity(0.72), .black.opacity(0.12), .black.opacity(0.78)],
startPoint: .top,
endPoint: .bottom
)
.ignoresSafeArea()
sideSelectionOverlay(round: round)
if let feedback = swipeFeedback {
swipeFeedbackOverlay(feedback)
}
VStack(spacing: 0) {
header(round: round, remaining: remaining)
Spacer()
if let bannerMessage {
banner(message: bannerMessage)
.padding(.horizontal, HermesTheme.screenPadding)
.padding(.bottom, 16)
}
bottomOverlay(round: round, timerLocked: timerLocked)
}
swipeSurface(round: round, remaining: remaining, timerLocked: timerLocked, bannerMessage: bannerMessage)
}
.offset(x: dragOffset.width * 0.12)
.contentShape(Rectangle())
.gesture(swipeGesture(round: round, timerLocked: timerLocked))
.onAppear {
@@ -113,6 +84,43 @@ struct RoundView: View {
}
}
private func swipeSurface(round: HermesRound?, remaining: TimeInterval, timerLocked: Bool, bannerMessage: String?) -> some View {
ZStack {
HermesTheme.surface
.ignoresSafeArea()
if round != nil {
HermesVideoPlayerView(coordinator: playerCoordinator, cornerRadius: 0, fixedHeight: nil)
.ignoresSafeArea()
}
LinearGradient(
colors: [.black.opacity(0.72), .black.opacity(0.12), .black.opacity(0.78)],
startPoint: .top,
endPoint: .bottom
)
.ignoresSafeArea()
if let feedback = swipeFeedback {
swipeFeedbackOverlay(feedback)
}
VStack(spacing: 0) {
header(round: round, remaining: remaining)
Spacer()
if let bannerMessage {
banner(message: bannerMessage)
.padding(.horizontal, HermesTheme.screenPadding)
.padding(.bottom, 16)
}
bottomOverlay(round: round, timerLocked: timerLocked)
}
}
.offset(x: dragOffset.width * 0.72)
.rotationEffect(.degrees(max(min(Double(dragOffset.width / 40.0), 6), -6)))
.shadow(color: .black.opacity(0.28), radius: 22, x: 0, y: 10)
}
private func header(round: HermesRound?, remaining: TimeInterval) -> some View {
HStack(alignment: .top, spacing: 16) {
VStack(alignment: .leading, spacing: 10) {
@@ -243,7 +251,7 @@ struct RoundView: View {
@ViewBuilder
private func sideSelectionOverlay(round: HermesRound?) -> some View {
if let round, phase == .preview {
let dragThreshold: CGFloat = 18
let dragThreshold: CGFloat = 10
let isDraggingRight = dragOffset.width > dragThreshold
let isDraggingLeft = dragOffset.width < -dragThreshold
@@ -252,13 +260,13 @@ struct RoundView: View {
title: sortedOutcomes(for: round).dropFirst().first.map(outcomeTitle) ?? localization.string(for: "round.no"),
subtitle: localization.string(for: "round.swipe_left"),
color: .red,
opacity: isDraggingLeft ? min(abs(dragOffset.width) / 180.0, 0.78) : 0
opacity: isDraggingLeft ? min(abs(dragOffset.width) / 240.0, 0.95) : 0
)
sideTint(
title: sortedOutcomes(for: round).first.map(outcomeTitle) ?? localization.string(for: "round.yes"),
subtitle: localization.string(for: "round.swipe_right"),
color: HermesTheme.positive,
opacity: isDraggingRight ? min(abs(dragOffset.width) / 180.0, 0.78) : 0
opacity: isDraggingRight ? min(abs(dragOffset.width) / 240.0, 0.95) : 0
)
}
.ignoresSafeArea()
@@ -287,7 +295,7 @@ struct RoundView: View {
}
private func swipeGesture(round: HermesRound?, timerLocked: Bool) -> some Gesture {
DragGesture(minimumDistance: 24)
DragGesture(minimumDistance: 12)
.onChanged { value in
dragOffset = value.translation
}
@@ -298,7 +306,7 @@ struct RoundView: View {
return
}
guard abs(value.translation.width) > 72, abs(value.translation.width) > abs(value.translation.height) else {
guard abs(value.translation.width) > 145, abs(value.translation.width) > abs(value.translation.height) else {
swipeFeedback = nil
return
}
@@ -322,7 +322,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HermesPrediction.app/HermesApp";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HermesPrediction.app/HermesPrediction";
};
name = Debug;
};
@@ -344,7 +344,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HermesPrediction.app/HermesApp";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HermesPrediction.app/HermesPrediction";
};
name = Release;
};
@@ -1,5 +1,5 @@
import XCTest
@testable import HermesApp
@testable import HermesPrediction
private struct TestFailure: Error, CustomStringConvertible {
let description: String